Theme System Hardening Checklist
Why this exists
Theming failures are expensive because they break first impressions, accessibility, and trust. This checklist is designed for release day and post-release monitoring.
Architecture baseline
- preference state: light, dark, system
- system state from media query
- resolved state used by document root
- CSS tokens mapped through semantic variables
Pre-release checklist
SSR and first paint
- inline bootstrap sets data-theme before app hydration
- no first paint flash on both light and dark
- hydration warnings remain at zero
State and persistence
- invalid storage values are sanitized
- cross-tab sync works via storage event
- preference survives full restart
Accessibility and contrast
- critical semantic pairs pass AA
- focus ring tokens are visible in both themes
- disabled and muted states are visually distinct
UI behavior
- theme toggle icon communicates next action
- aria labels match the actual action
- transitions are smooth and non-blocking
Reference implementation
export function resolveTheme(pref: "light" | "dark" | "system", system: "light" | "dark"): "light" | "dark" {
return pref === "system" ? system : pref;
}
export function applyTheme(theme: "light" | "dark", doc: Document = document) {
doc.documentElement.setAttribute("data-theme", theme);
}Release telemetry
Track event counts and rates for:
- theme_preference_changed
- theme_resolved
- theme_bootstrap_fallback_used
- theme_contrast_validation_failed
Incident response
If regressions appear:
- freeze theme token changes
- roll back last token rename
- run contrast validator and store tests
- patch bootstrap script first
Done definition
Theme work is complete only when store tests, e2e smoke, and contrast checks pass in CI and in local preflight.