Dark mode

The dark: modifier, the class and media strategies, the persisted toggle and server rendering.

1 min readv1

The dark: modifier#

HTML
Switch the site theme to see this change.
<div nib="p=18px; bdrd=12px; bgc=#fff; cl=#0f172a; bd=1px solid #e2e8f0; dark:bgc=#0f172a; dark:cl=#e2e8f0; dark:bd=1px solid #1e293b">
  Switch the site theme to see this change.
</div>

Strategies#

darkMode

Output

Use when

"class" (default)

.dark .nib-x { … }

users choose a theme

"media"

@media (prefers-color-scheme: dark) { … }

follow the OS, no toggle

false

nothing

the product has no dark theme

The toggle (class strategy)#

runtime.toggleDarkMode() flips the dark class on <html>, returns the new state, stores it in localStorage and follows the operating system until the user decides.

HTML
<button onclick="Nib.toggleDarkMode()">Theme</button>

Avoiding a light flash on load (SSR)#

With server rendering the HTML arrives before any JavaScript. If the user chose dark, apply the class before the first paint with a tiny inline script in <head>:

HTML
<script>
  try {
    const saved = localStorage.getItem("nib-dark");
    const dark = saved !== null ? saved === "true" : matchMedia("(prefers-color-scheme: dark)").matches;
    if (dark) document.documentElement.classList.add("dark");
  } catch {}
</script>

Designing both themes#

  • Put paired colours in recipes (card, input) so each surface declares its dark variant once.
  • Or use CSS variables: set --surface in :root and .dark, and write bgc=var(--surface) everywhere.
  • Re-check contrast: muted text usually needs a lighter step in dark.

Next steps#