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.
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.