A browser extension that automatically darkens overly bright web pages using WCAG 2.1 relative luminance calculations.
For every element on the page, the extension computes the relative luminance of its background color using the WCAG 2.1 sRGB formula. Elements whose luminance exceeds the configured threshold are overridden with a dark background (#1a1a1a), and text colors are adjusted to maintain readable contrast.
Transparent elements are handled by walking up the parent chain to find the effective background — this prevents accidentally forcing white text onto a non-darkened light ancestor (e.g., a colored notification bar).
- WCAG 2.1 luminance detection — accurate perceptual brightness calculation
- Threshold slider — configure sensitivity from 50% to 100% via the popup UI
- SPA support —
MutationObserverwatches for dynamically added nodes (cards, modals, infinite scroll) - Non-destructive — original inline styles are saved in
data-adm-originaland fully restored when the threshold changes - Minimal permissions — only
storageis requested
auto-dark-mode/
├── manifest.json # Manifest V3
├── content.js # Core logic: luminance detection, CSS injection, MutationObserver
├── popup.html # Popup UI with threshold slider
├── popup.js # Reads and writes threshold via chrome.storage.sync
└── icons/
├── icon16.png
├── icon32.png
├── icon48.png
└── icon128.png
- Open
chrome://extensionsand enable Developer mode. - Click Load unpacked and select the
auto-dark-mode/directory. - Visit any bright page to see it in action.
Convert the extension using the Safari Web Extension converter:
xcrun safari-web-extension-converter /path/to/auto-dark-mode \
--project-location ./safari-build \
--bundle-identifier com.yourname.auto-dark-mode \
--app-name "Auto Dark Mode"Then build and run the generated Xcode project, and enable the extension in Safari → Settings → Extensions.
Click the extension icon to open the popup. Use the slider to adjust the brightness threshold:
- Higher value (e.g. 95%) — only pure-white backgrounds are darkened
- Lower value (e.g. 50%) — most light-colored backgrounds are darkened
Changes take effect immediately across all tabs.
| Scenario | Handling |
|---|---|
Transparent background (rgba(0,0,0,0)) |
Walk parent chain to find effective background |
CSS variables (var(--bg)) |
getComputedStyle resolves to a concrete RGB value |
IMG / VIDEO / SVG / CANVAS |
Skipped via shouldSkip() |
| Already-dark pages | Luminance ≤ threshold → no change |
| SPA dynamic content | MutationObserver with accumulated pendingNodes to avoid drop-outs on rapid DOM updates |
| Site JS rewrites inline style on hover | Kept dark via injected stylesheet selector ([data-adm-*] { ... !important; }) |
| Rollback on threshold change | Restored from data-adm-original attribute |