Explicit choice wins
Light or dark overrides the operating-system preference.
Light and dark are complete semantic palettes. Auto is the preference resolver that chooses between them. This guide explains how to build accessible color, typography, scrolling, and browser integration around that model.
Model what the user selected separately from what the interface currently renders. This keeps Auto responsive to operating-system changes without losing the explicit preference.
Light or dark overrides the operating-system preference.
The effective theme updates as the system appearance changes.
Store auto, light, or dark. Do not store Auto’s current result as the source of truth.
Do not invert raw colors. Remap semantic roles such as canvas, surface, text, border, action, focus, warning, and elevation.
Components should consume meaning. Foundation colors can change without forcing each component to understand theme mechanics.
Application background and largest visual field.
--color-canvas
Cards, forms, drawers, and elevated regions.
--color-surface
Primary interactive emphasis with a paired foreground.
--color-action
Raw ramps such as gray-50, blue-500, red-700.
Intent such as text-primary, border-control, action, danger.
Local bindings such as button-primary-bg and input-focus-ring.
--white or --light-gray
These names become false as soon as the theme changes.
--surface-raised or --text-secondary
The role remains stable even when the value changes.
Light mode should not default to pure white and black everywhere. Dark mode should not collapse every surface into black.
Use white or a near-white above a slightly tinted canvas.
Use tonal steps and explicit control boundaries.
Leave room for surfaces to become visibly lighter.
Use layers, borders, and restrained shadows together.
Highly saturated accents can appear unusually luminous on dark backgrounds. Increase lightness when needed, reduce chroma for large fills, and revalidate every foreground/background pair.
Contrast is a property of a rendered pair, not a color token in isolation. Validate every state against its actual adjacent background.
| Content | WCAG AA | Preferred internal target |
|---|---|---|
| Normal text | 4.5:1 | 7:1 when practical |
| Large text | 3:1 | 4.5:1 |
| Controls and meaningful graphics | 3:1 | 3.5–4.5:1 |
| Secondary meaningful text | 4.5:1 | 5–7:1 |
Metadata, dates, help text, and supporting descriptions are still meaningful text.
Use a two-layer focus treatment so it remains visible against the control and the surrounding canvas.
Pair status color with labels, icons, border treatment, shape, or placement.
Use sRGB as the required baseline. Use OKLCH to construct palettes. Use Display P3 only as an enhancement after the fallback already works.
sRGB: compatibility baseline for UI, screenshots, remote desktops, and mixed displays.
Display P3: progressive enhancement for richer supported displays.
OKLCH: perceptual palette authoring, interpolation, and tonal ramp construction.
Perceptual lightness makes ramps easier to reason about than HSL. It does not replace contrast measurement.
Every sRGB fallback and P3 override must independently pass contrast and preserve state meaning.
Global font-smoothing overrides are non-standard and can make text appear thinner, especially bright text against dark backgrounds.
Do not force antialiasing globally.
font-optical-sizing: autoUse variable-font optical sizing where available.
Review dense dark-mode text on ordinary-density displays.
They are platform-specific and often thin the text.
optimizeLegibilityBehavior varies and can alter ligatures and kerning.
Preserve user-agent readability behavior.
Native scrolling retains platform behavior, keyboard access, touch momentum, history restoration, find-in-page, and assistive-technology compatibility.
Apply smooth scrolling to programmatic navigation, then disable it for reduced motion.
Use scroll-padding-top and scroll-margin-top.
Use scrollbar-gutter: stable where classic scrollbars affect width.
Use overscroll-behavior on nested panes, not indiscriminately on the page.
High contrast, forced colors, reduced motion, and reduced transparency are distinct user needs. Do not collapse them into dark mode.
Allow the browser to replace author colors. Use system color keywords for the few properties that remain controlled.
Strengthen text hierarchy, control boundaries, and focus indicators. Do not simply push every value toward black or white.
Treat opaque, deterministic surfaces as the baseline. Blur and translucent materials should be progressive decoration, never the only support for readable text.
The CSS below gives Auto to the browser and constrains the scheme only when the user explicitly chooses light or dark.
:root {
color-scheme: light dark;
--canvas: light-dark(#f7f8fa, #0f1115);
--surface: light-dark(#ffffff, #171a21);
--text-primary: light-dark(#171a1f, #f2f4f7);
--text-secondary: light-dark(#545b66, #b5bcc7);
--border-control: light-dark(#7c8592, #697386);
--action: light-dark(#155eef, #8ab4ff);
--focus: light-dark(#005fcc, #9cc2ff);
}
:root[data-theme="light"] {
color-scheme: only light;
}
:root[data-theme="dark"] {
color-scheme: only dark;
}
html {
text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
scrollbar-gutter: stable;
scroll-padding-top: var(--header-offset, 4.5rem);
scroll-behavior: smooth;
}
body {
background: var(--canvas);
color: var(--text-primary);
font-optical-sizing: auto;
text-rendering: auto;
}
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
}
@media (prefers-contrast: more) {
:root {
--text-secondary: var(--text-primary);
}
}
@media (forced-colors: active) {
:focus-visible {
outline-color: Highlight;
}
}
A theme is a state system. Validate transitions, preferences, display characteristics, scaling, loading behavior, and content variation.
Theme systems should map stable roles to adaptive values.
WCAG 2.2, forced colors, and real-device tests are release gates.
OKLCH, P3, blur, and rich materials improve capable devices without weakening the baseline.
The hardest failures are structural: missing semantic roles, treating Auto as a palette, using opacity as hierarchy, overriding font rasterization globally, and validating only one appearance.
The explainer includes visible references, structured TechArticle metadata, and
section-level data-source-refs mappings. This keeps the research lineage
available to readers, parsers, and future maintainers.
Browser-support claims should be revalidated before adopting new features as mandatory dependencies. The linked standards remain the source of truth when advisory guidance differs.