Browser dark mode used to mean hunting through settings, installing heavy extensions that slowed everything down, or just living with eye strain. I built NightOwl because none of the options were right.
The Problem With Existing Dark Mode Extensions
Most dark mode extensions fall into one of two categories:
Too heavy — Some popular dark mode extensions load MB of CSS per page, slow down rendering, and have dozens of config options most people never touch. For users who just want "dark, please" — it's overkill.
Too basic — The simple ones invert colors, which makes images look weird (inverted photos are jarring) and doesn't handle edge cases like videos.
What NightOwl Does Differently
NightOwl applies smart dark mode to every website with one click. Key design decisions:
Smart Color Inversion
Instead of inverting everything, NightOwl targets background colors and text colors specifically. Images, videos, and elements with background-image are excluded from inversion. This means your photos still look like photos.
Schedule Mode
Night owls don't all work the same hours. NightOwl lets you set a start time and end time for dark mode — it activates and deactivates automatically. For remote workers who start at 10pm or students pulling late-night study sessions, this is genuinely useful.
Per-Site Whitelist
Some sites look great in dark mode. Others (certain dashboards, design tools) look broken. NightOwl remembers per-site settings, so you can keep LinkedIn dark while keeping Figma light.
Technical Approach
The extension works through a combination of CSS injection and DOM manipulation:
/* Core dark mode CSS */
:root {
--nightowl-bg: #1a1a2e;
--nightowl-text: #e8e8e8;
--nightowl-link: #8eb4f8;
}
/* Applied to body */
body {
background-color: var(--nightowl-bg) !important;
color: var(--nightowl-text) !important;
}
/* Smart image handling */
img, video, canvas, [style*="background-image"] {
filter: none !important;
}
The schedule feature uses browser.alarms — Firefox's built-in alarm API that wakes up the service worker at the scheduled time without needing the extension to be always running.
The Inversion Algorithm
Pure CSS inversion (filter: invert(1)) on the root element flips everything including images. A better approach:
- Apply a dark background/text color scheme to root
- Walk through all elements that have an explicit light background color (
background-color,backgroundshorthand) - Replace light backgrounds with dark equivalents using HSL manipulation
- Skip elements with media content
function adjustBackgrounds(root) {
const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
let node;
while ((node = walker.nextNode())) {
const computed = window.getComputedStyle(node);
const bg = computed.backgroundColor;
if (isLight(bg)) {
node.style.setProperty('background-color', darken(bg), 'important');
}
}
}
function isLight(color) {
// Parse RGB and check luminance
const rgb = parseRgb(color);
if (!rgb) return false;
const luminance = (0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.b) / 255;
return luminance > 0.5;
}
Performance Considerations
Dark mode injection runs on every page load. A few things I did to keep it fast:
Debounced MutationObserver — For SPAs that update the DOM, use a debounced observer rather than listening to every individual change.
CSS Variables First — Many modern sites already use CSS variables. Setting the variable on :root is one operation that cascades everywhere, instead of walking the DOM.
No blocking main thread — All DOM manipulation runs in a requestAnimationFrame callback to avoid layout thrashing.
Try It
NightOwl is free, no subscription, no tracking. The whole extension is open source:
NightOwl Dark Mode on Firefox AMO
If you're interested in the full set of Firefox productivity tools I've built: see the overview post here.
Top comments (0)