TIL: Dim too-bright pages with a Tampermonkey overlay

I run macOS, every app, and every website in dark mode. But plenty of sites ignore prefers-color-scheme and stay blinding white — or ship a dark theme that still leaves one giant white panel. So I wrote a Tampermonkey userscript that drops a translucent dark layer over near-white pages, with macOS-style hotkeys to nudge the brightness. The whole thing is one trick: a fixed, full-viewport <div> floating on top of everything.

The overlay. It’s a position: fixed div covering 100% × 100% at a huge z-index, tinted with a semi-transparent color. The key line is pointer-events: none — clicks, scroll, and text selection pass straight through, so you’re looking through tinted glass, not covering the page with a click-blocker:

overlay.style.cssText = `
  position: fixed;
  top: 0; left: 0;
  width: 100%; height: 100%;
  background: rgba(15, 8, 0, 0.16); // 👉 a hair of warm yellow, not neutral gray
  pointer-events: none;             // 👉 the whole reason this works
  z-index: 99999999;
`;

The tint is 15, 8, 0 — a touch of warm yellow rather than flat gray, which reads softer at night. Swap OVERLAY_RGB to '0, 0, 0' for plain gray.

Only dim when it makes sense. In auto mode the overlay appears only when both conditions hold: the system is in dark mode, and the page background is actually near-white. It samples body, documentElement, and main, parses the computed backgroundColor, and calls it white when every channel clears 240 — so a page that’s already dark is left alone:

function run() {
  if (userControlled) { /* manual wins, see below */ }
  const isDarkMode  = darkModeQuery.matches;          // matchMedia('(prefers-color-scheme: dark)')
  const isWhitePage = isBackgroundWhite();            // r,g,b all > 240
  toggleOverlay(isDarkMode && isWhitePage);
}

Manual control wins and sticks. The moment I press a hotkey, the script flips a userControlled flag and writes the opacity (and on/off state) to localStorage, which is scoped per origin. On the next load, a stored value short-circuits detection entirely — that site stays exactly where I left it, forever:

const stored = localStorage.getItem(STORAGE_KEY);
if (stored !== null) {
  opacity = clamp(parseFloat(stored));
  userControlled = true; // 👉 from now on, ignore auto-detection on this site
}

Hotkeys + HUD. ctrl+alt+cmd+F1 darkens, F2 lightens, F3 toggles on/off. The listener runs in the capture phase and calls preventDefault() so pages can’t swallow the combo. Each press flashes a macOS-style brightness HUD — a sun icon over a level bar — that fades after 2s. The bar tracks brightness, not opacity, so it’s inverted:

const level = enabled ? 1 - opacity / MAX : 1; // more dimming → emptier bar

Download make-white-pages-darker.user.js — open it in Tampermonkey to install, or paste it into a new userscript.

Because it re-runs on DOMContentLoaded and listens for theme changes (darkModeQuery.addEventListener('change', run)), flipping macOS between light and dark updates every open tab live.


Let's stay connected!

If you like the post, subscribe to my newsletter to get latest updates:

Author

I'm Oliver Nguyen. A software maker working mostly in Go and JavaScript. I enjoy learning and seeing a better version of myself each day. Occasionally spin off new open source projects. Share knowledge and thoughts during my journey.