DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

Building a New Tab Extension: Why I Chose No Framework

When I started building Weather & Clock Dashboard — a Firefox new tab extension that shows live weather and world clocks — I had a choice: use a modern framework or go vanilla.

I went vanilla. Here's why, and what I learned.

The Case Against Frameworks in Extensions

1. Bundle size matters more in extensions

A React app might add 40KB+ of compressed JS. In a web app that's negligible. In a browser extension, that's loaded on every new tab, blocking render. Users feel it.

My entire extension is ~15KB uncompressed. It renders instantly.

2. No build tooling = no attack surface

Supply chain attacks on npm packages are real. Zero dependencies means:

  • No npm install
  • No lockfile drift
  • No audits needed
  • No node_modules to accidentally commit

The security model is simple: I wrote all the code, all the code is right here.

3. Extensions live in a privileged context

Browser extensions have access to browser APIs, storage, and (with permissions) cross-origin requests. Introducing a framework that assumes a normal web context can cause subtle issues.

Vanilla JS has direct, obvious access to browser.storage, browser.tabs, etc. No abstraction layer to debug.

What I Actually Used

weather-clock-dashboard/
├── manifest.json
├── newtab.html
├── style.css
└── app.js
Enter fullscreen mode Exit fullscreen mode

That's the entire extension. No src/, no dist/, no build step.

State management

I didn't need Redux. Local state lives in module-scoped variables. Persistent state uses browser.storage.local:

// Save user preferences
async function savePrefs(prefs) {
  await browser.storage.local.set({ prefs });
}

// Load on startup
async function loadPrefs() {
  const { prefs } = await browser.storage.local.get('prefs');
  return prefs || DEFAULTS;
}
Enter fullscreen mode Exit fullscreen mode

Reactivity

Instead of a virtual DOM, I update the DOM directly when data changes:

function updateWeather(data) {
  document.getElementById('temp').textContent = `${Math.round(data.temperature)}°`;
  document.getElementById('condition').textContent = getConditionText(data.weathercode);
  document.getElementById('weather-icon').textContent = getWeatherIcon(data.weathercode);
}
Enter fullscreen mode Exit fullscreen mode

It's fast. No diffing overhead, no reconciliation. Just set the value.

Dark/light mode

CSS custom properties + a data attribute:

:root {
  --bg: #ffffff;
  --text: #1a1a1a;
}

[data-theme="dark"] {
  --bg: #1a1a2e;
  --text: #e0e0e0;
}
Enter fullscreen mode Exit fullscreen mode
document.documentElement.setAttribute('data-theme', prefs.theme);
Enter fullscreen mode Exit fullscreen mode

Two lines to switch themes. No CSS-in-JS needed.

Where This Approach Falls Short

I won't pretend vanilla is always the right call:

  • Complex UI state (forms with many dependent fields, wizard flows) gets messy fast
  • Large teams benefit from component conventions that frameworks enforce
  • Accessibility — frameworks often handle ARIA roles better than hand-rolled JS

For a single-page, single-purpose tool like a new tab extension? Vanilla is plenty.

The Result

The extension works, loads instantly, and has zero runtime errors related to framework incompatibilities. If something breaks, I look at ~300 lines of JS I wrote myself.

Try it on Firefox or view the source — MIT licensed.


Have you shipped a browser extension? What stack did you use? I'm curious whether most extension authors reach for frameworks or go vanilla.

Top comments (0)