Overview
Dark mode theme switching where color tokens (CSS custom properties) animate across the swap via transition: background-color, color, ... declarations. A stale-state guard listens for the Page Visibility API and snaps to the new theme if the tab was hidden mid-transition.
When to use it
Reach for token-transition fallback on user-facing theme toggles where the visual continuity reduces jarring. Skip the animated transition when the change happens during a load (theme-from-system on first paint) — the transition would animate from the wrong initial state. Skip it for theme switches that depend on actively-displayed media (use a non-animated swap to avoid color flicker on video).
How it works
Define color tokens as CSS custom properties on :root and :root[data-theme="dark"]. Toggle the data-theme attribute to swap themes. For the cross-fade, apply html { transition: background-color 240ms ease, color 240ms ease } plus matching transitions on common surface elements (cards, buttons, borders). The custom properties themselves are not animatable directly — only properties that use the custom properties (background-color, color, border-color, fill) are. The stale-state guard listens for document.visibilitychange and, if the tab was hidden mid-transition, calls document.documentElement.style.transition = 'none' to snap to the final theme.
Production gotchas
Applying the transition on every element produces a cascade of paints; restrict the transition selector to the root + named surfaces only. Tokens used in box-shadow do not transition smoothly when shadow color is changing — the browser interpolates only between identical shadow geometries. For first-paint, the transition must be disabled until after hydration or the user sees the light theme briefly flash before the dark theme animates in. Use a data-no-transitions attribute on <html> at boot, removed after the first idle callback.
Accessibility
Honor prefers-color-scheme at first paint — only animate the transition when the user explicitly clicks the toggle, not when the system preference changes underneath the page. Under prefers-reduced-motion: reduce drop the transition entirely (instant theme swap) since color cross-fades can be uncomfortable for users sensitive to contrast changes. Verify all token pairs meet WCAG AA contrast in both themes — the swap should not introduce contrast regressions.
References
Implementation depth
Theme transitions are safest when tokens change, not individual components. Switch data-theme or root variables once, then let surfaces consume color custom properties from a single source of truth.
Beware page visibility and first paint. A theme transition that runs before hydration can flash the wrong scheme, so persist the chosen theme early and disable decorative color interpolation under reduced motion.