A layered approach to building a persistent theme and mode toggle for websites. The pattern separates 'mode' (light/dark) from 'theme' (accent color) using separate data attributes, then implements three progressive enhancement layers: semantic HTML with radio inputs, a tiny blocking inline script to prevent flash of wrong theme on load, and a deferred script for interactivity and OS preference tracking. Key decisions covered include using localStorage over cookies or IndexedDB, storing user intent ('system') rather than resolved values, avoiding mixing data-mode CSS selectors with prefers-color-scheme media queries, and accessibility requirements for keyboard and screen reader users. The author also shares that they ultimately removed this feature from their own site, concluding that system preference alone is often sufficient.
Table of contents
The HTML Structure (Layer 1)The Three-Layer ApproachSee It in ActionWhy localStorage (and not cookies or IndexedDB)Avoiding the Flash: A Blocking Head Script (Layer 2)Setting Up Persistence (Layer 3)Handling "System" Mode ProperlyStyling the Radio Inputs (and Focus)Accessibility NotesGotchas & Things to Watch ForWhy I Removed This From My SiteClosing Thoughts856 Impressions1 Comment