Themes

The theme ships with light and dark palettes built on the light-dark() CSS function. A visitor’s preference is persisted in localStorage and applied before paint so the page never flashes the wrong palette during load.

The switch

A small button in the header toggles between three states: light, dark, and auto (follow the OS preference). The current state is stored under apidocs-theme in localStorage.

The switch is a Web Component — no framework, no client-side router. Toggling it sets data-theme on the <html> root, which cascades into every light-dark() value declared in the theme’s tokens.

FOUC prevention

The layout includes a tiny inline script in <head> that reads the saved preference and applies it to <html> before the browser paints the first frame. That avoids the brief flash of light theme that’d otherwise show while the page loads in dark mode.

<script>
  // Runs synchronously before any styles paint.
  try {
    const saved = localStorage.getItem("apidocs-theme");
    if (saved === "light" || saved === "dark") {
      document.documentElement.setAttribute("data-theme", saved);
    }
  } catch (e) { /* private mode etc. */ }
</script>

The try/catch is there because Safari throws on localStorage access from private windows. When the saved value is missing or invalid, the OS preference wins via prefers-color-scheme in CSS.

Color tokens

All theme-aware colors are CSS custom properties resolved with light-dark() in tokens.css:

:root {
  --bg:       light-dark(#ffffff, #0d1117);
  --fg:       light-dark(#1f2328, #e6edf3);
  --rule:     light-dark(#d0d7de, #30363d);
  --accent:   light-dark(#0969da, #58a6ff);
}

A single source of truth for both palettes means there’s no separate .dark stylesheet to keep in sync. Authoring new colors means adding one token; the active data-theme decides which value the browser resolves.

Theme-aware images

For screenshots, diagrams, or any image whose light/dark variant is meaningfully different, ship both and let CSS pick:

<figure>
  <img class="light" src="images/dashboard-light.png" alt="…">
  <img class="dark"  src="images/dashboard-dark.png"  alt="…">
  <figcaption>Dashboard, shown in the current theme.</figcaption>
</figure>

Both images go through the image pipeline as usual. CSS visibility rules (not JavaScript) decide which one is shown, so the toggle is instant and there’s no flicker on theme change. See Images → Light and dark variants for the rendered example.

Code syntax themes

Shiki highlights every code block with a paired theme — one for light, one for dark. The pipeline emits both color sets up front; switching themes swaps which one is visible via a CSS variable, with no client-side JavaScript involved in the actual repaint.

The defaults are github-light and github-dark-dimmed. They’re chosen for contrast with the body palette without being so saturated they jump off the page when you scroll past a code block.

Customizing

Color tokens live in apidocs/styles/tokens.css. To override them in a consumer project, ship a stylesheet that comes after the theme’s and redeclare what you need on :root — every consumer rule wins through normal cascade order. A custom build hook can register that stylesheet through Eleventy’s addPassthroughCopy the same way the theme registers its own assets.

Test both palettes

New color tokens, new SVG fills, and new screenshots all need a round-trip through the theme switch. The light/dark switch is one click away in the header — use it before each commit to catch contrast regressions early.