Dark Mode Enabler is an open-source browser extension that applies dark, sepia or grayscale filters to websites that have no dark mode of their own β so you can stop staring into a white screen at night.
Built with WXT and TypeScript. Runs on Chrome, Firefox and Edge from a single codebase.
π§© Install from the Chrome Web Store
- Four display modes β Dark, Sepia, Grayscale, or Disabled.
- Knows when to stay out of the way β badges sites that already have their own dark theme, so you don't invert a dark page into a light one.
- 13 languages β English, Spanish, Portuguese (BR), Russian, German, French, Italian, Japanese, Korean, Chinese (Simplified), Turkish, Polish and Indonesian.
- Tune the look β brightness, contrast and warmth controls.
- Per-site memory β your choice is remembered for each website.
- No flash of white β the theme is applied before the page renders.
- Keeps layouts intact β sticky headers, modals and popovers keep working.
- Photos stay photos, logos stay visible β images are classified so photographs are not shown as negatives and dark logos do not vanish into the background.
- Instant across tabs β changing a mode updates every open tab of that site.
- No tracking β no analytics, no telemetry, no network requests at all.
demo.mp4
- Install from the Chrome Web Store.
- Open a site you want to theme and click the extension icon.
- Pick a display mode. It is remembered for that site.
- storage β Stores the display mode you picked for each website, so your choice survives restarting the browser. Nothing else is stored, and nothing leaves your device.
- host permissions (
*://*/*) β Required to inject the stylesheet on any site you might want to theme, before the page paints. The extension does not read page content and makes no network requests. See privacy-policy.md.
Node.js 20 or newer.
npm installThis also runs wxt prepare, which generates the TypeScript types in .wxt/.
npm run devWXT builds the extension, launches a fresh browser profile with it already installed, and hot-reloads on save. For Firefox:
npm run dev:firefoxnpm run buildOutput goes to .output/chrome-mv3/. To build every target:
npm run build:allThe extension must be built before it can be loaded β extension/ is source, not a loadable extension.
Chrome / Edge / Brave
npm run build- Open
chrome://extensions(oredge://extensions,brave://extensions). - Turn on Developer mode (top-right toggle).
- Click Load unpacked.
- Select the
.output/chrome-mv3folder in this repository.
The Dark Mode Enabler icon appears in your toolbar. After a rebuild, click the reload icon on the extension's card to pick up changes.
Firefox
npm run build:firefox- Open
about:debugging#/runtime/this-firefox. - Click Load Temporary Add-onβ¦
- Select
.output/firefox-mv2/manifest.json.
Temporary add-ons are removed when Firefox restarts.
npm run zip:allProduces store-ready ZIPs in .output/, including the sources ZIP that Firefox reviewers require.
npm run compilewxt.config.ts Manifest + per-browser overrides (generated, don't hand-edit)
extension/
βββ entrypoints/
β βββ background.ts Service worker β migrates pre-1.2 settings
β βββ content/
β β βββ index.ts Sets data-dme-mode on <html> at document_start
β β βββ theme.css All theme rules
β βββ popup/ Mode picker; owns persistence
βββ lib/settings.ts Modes and storage keys β single source of truth
βββ public/icon/ Extension icons
The popup writes the chosen mode to browser.storage.local; the content script listens on storage.onChanged and sets an attribute on <html> that theme.css keys off. There is no message passing between them, so there is nothing to fail silently on pages where a content script cannot run.
Two details worth knowing:
- The filter is applied to
<html>, not<body>. The Filter Effects spec exempts the document root from the rule that a filtered element becomes the containing block for fixed-position descendants. On<body>it breaks every sticky header on the page; on<html>it does not. - Theme CSS is listed in the manifest's
content_scripts.cssarray, so the browser applies it before the first paint. This is what removes the white flash, and it is why the CSS is imported by the content script entrypoint rather than injected from JavaScript.
More detail, including the invariants that should not be broken, is in AGENTS.md. Design decisions are recorded in docs/adrs.
This repository also hosts the small site published at codesandtags.github.io/dark-mode-extension. It is separate from the extension:
npm run site:dev
npm run site:build
npm run site:deployContributions are welcome. If a site renders badly with a mode enabled, please open an issue with the URL and a screenshot β that is the most useful kind of report this project can get.
Before opening a PR, run npm run compile and check your change against a page with a fixed header, a modal dialog and a photograph. See AGENTS.md for the reasoning behind the current design.
Chrome 114+, Edge 114+, Firefox 115+. Works on any page an extension is allowed to inject into β browser settings pages, the web stores and PDF viewers are excluded by the browser itself, and the popup will tell you when that is the case.
Dark Mode Enabler is not affiliated with any website or company. It is an independent project created by Edwin Torres.
Released under the MIT License.
Questions, suggestions or feedback: codesandtags@gmail.com, or the GitHub issue tracker.