## Outline
- Problem: wrong theme on first paint when preference lives only in `localStorage`.
- Mitigation: tiny inline head script setting `data-theme` before CSS evaluates.
- Tokens vs hex: semantic colors across Emerald / Night / Corporate / etc.
- Contrast and interactive states per theme.
- Respect `prefers-reduced-motion` for theme transitions.
## Draft
Theme toggles demo beautifully in talks. In production they earn their reputation with small indignities: a flash of the wrong palette while JavaScript wakes up, focus traps inside fancy menus, hydration weirdness when you bolt a heavyweight client runtime on later.
Rails plus Tailwind plus daisyUI can stay comparatively calm if you admit one fact: **`localStorage` alone is late**. The browser will paint once with whatever the HTML implied before your bundle runs.
### Win the race before CSS settles
Put a **tiny inline script in `<head>`** that reads the saved theme (cookie or `localStorage`, your call) and sets **`data-theme`**—or whichever attribute your daisyUI setup expects—before the main stylesheet wins. Pair that with **semantic tokens** (`primary`, `base-100`, …) instead of scattering raw hex utilities through every partial. When you rename a theme or add another, tokens keep the facelift survivable.
### Let variables do the lifting
Tailwind 4 alongside daisyUI leans hard on CSS variables backing semantic colors. One-off **`#336699`** sprinkles age poorly once you ship four skins. Tokens keep headings, cards, and focus rings speaking the same language.
### Actually check contrast—per theme
A palette can look luscious on the hero section and fall apart on long-form prose. Flip through **Emerald**, **Corporate**, whatever you ship, and sanity-check body copy and **focus rings** in each—not only the marketing chrome.
### Motion is optional for humans too
Some readers get motion sick from animated theme crossfades. Honor **`prefers-reduced-motion`** with an instant swap. Glamour can take a back seat to stomachs.