Foundations
Theming
One foundation, three themes. Light, Dark and Accessibility share every structural token —
typography, spacing, radius, motion — and only re-map colour and
shadow. A theme is selected by a single data-theme scope; the same
markup reskins with zero layout shift.
The same UI, three themes
Identical markup — only the data-theme on the wrapper changes.
A card, a badge and a button — reskinned by the scope alone.
A card, a badge and a button — reskinned by the scope alone.
A card, a badge and a button — reskinned by the scope alone.
How a theme is applied
Set the scope on <html>. Everything inside resolves its tokens from that theme.
<!-- whole document -->
<html data-theme="dark"> … </html>
<!-- or scope a single region (e.g. a theme preview) -->
<div data-theme="accessibility"> … </div> Switching at runtime
- Persist the choice to
localStorageand apply it with an inline pre-paint script in<head>— so the theme is set before first paint and there is no flash. - Accessibility is explicit. Never auto-flip to it from
prefers-contrastor system settings — let the user choose it, and keep their choice. - Because only colour and shadow change, switching never moves layout — no reflow, no CLS.
Adding a fourth theme
Define a new [data-theme="…"] scope that supplies the full colour and
shadow token set (copy a theme and re-map values — don't leave gaps, or nested scopes inherit the
wrong values). Structural tokens stay shared. Then add it to the theme switcher.
The full per-theme values live on the Color tokens and Shadow tokens pages; the high-contrast theme is detailed on Accessibility.