Skip to content

Architecture: Three-Layer Engine

Ultimate Dark Mode applies dark mode through three layers, each progressively more aggressive. Sites with a dedicated CSS override skip Layers 2 and 3 to avoid breaking app JavaScript.

Layer Flow

Layer 1: Native Dark Mode Forcing

Always applied. Zero side effects.

  • Sets color-scheme: dark on :root
  • Injects <meta name="color-scheme" content="dark">
  • Activates native dark mode on sites that already support prefers-color-scheme: dark

This layer alone handles well-built sites that implement their own dark theme. Most modern sites and web apps respond to this signal correctly.

Layer 2: Generic CSS (styles/darkmode.css)

Only applied when no site-specific override exists.

  • Applies an oklch-based dark palette to common HTML elements
  • All rules are scoped under html[data-darkmode]
  • Covers backgrounds, text colors, borders, inputs, and other standard elements

The oklch Color Palette

TokenValueUse
Base backgroundoklch(0.15 0.01 260)Page/app background
Surfaceoklch(0.20 0.01 260)Cards, panels, modals
Elevated surfaceoklch(0.25 0.01 260)Buttons, inputs
Borderoklch(0.30 0.01 260)Dividers, borders
Muted textoklch(0.65 0.01 260)Secondary text, placeholders
Body textoklch(0.88 0.01 260)Primary text
Heading textoklch(0.93 0.01 260)Headings, emphasis
Linkoklch(0.75 0.15 250)Links
Accentoklch(0.65 0.15 250)Active states, highlights

Layer 3: JS-Assisted (MutationObserver)

Only applied when no site-specific override exists.

  • Watches for DOM mutations and processes elements with inline styles
  • Remaps light backgrounds to dark equivalents
  • Debounced via requestAnimationFrame to avoid performance issues

This layer catches dynamically-injected inline styles that CSS alone cannot override.

Why Overrides Skip Layers 2 and 3

Complex web apps (Google Sheets, Notion, Slack, etc.) manage their DOM and read computed styles back in JavaScript. When our generic CSS injects oklch() values into elements these apps control, their JS crashes because they cannot parse oklch().

Real-world example: Google Sheets reads element.style.color, gets oklch(0.88 0.01 260), passes it to an internal color parser that expects rgb() format, and throws: Error in protected function: hg'oklch(0.88 0.01 260)' — crashing the entire app.

The fix is site-specific overrides that target only known-safe selectors (toolbar, sidebar, menus) and avoid touching elements that the app's JS manages. See ADR-001: No filter:invert() for more on this design decision.

Site Override Architecture

Override files follow strict rules:

  1. Wrapped in @layer darkmode.overrides { ... }
  2. Every selector scoped with html[data-darkmode]
  3. Never targets elements managed by the app's JavaScript
  4. Never uses filter: invert() or filter: brightness() on containers
  5. Preserves media elements (img, video, canvas, svg, picture, iframe)
  6. Prefers semantic selectors ([role="dialog"], [aria-label]) over fragile class names