Repository navigation
feat(theme): OS-follow color tokens via prefers-color-scheme (CL-8479) - #42
Conversation
ReviewVerdict: approve with nits. Dark tokens via prefers-color-scheme plus applyThemeToRoot .light toggle. No CI. No prior reviews. Should-fix: dark token block is duplicated byte-for-byte and will drift. Media query sets color tokens but not color-scheme: dark, so zero-JS dark can leave native controls light. |
Re-reviewVerdict: approve the theme should-fixes; Toaster suite still red. SHA 65f3636: color-scheme dark on media-query :root and .dark. Token blocks stay duplicated (light-dark() would break contrast-test); lockstep test fails if they diverge. Full bun test: 335 pass, 3 fail in tests/ui/toast.test.tsx (leftover-toast Expected 1 Received 2). Pre-existing vs this theme diff; a follow-up is in flight on this branch so CI is actually green. |
Re-reviewVerdict: approve. SHA 99f2eef. Toaster isolation: sonnerToast.dismiss() in afterEach before unmount. bun test 338 pass, 0 fail. Theme color-scheme and token lockstep unchanged. typecheck/lint 0. |
Review (Draper + Emil)Verdict: approve with docs nits. Zero-JS OS dark is the right default.
Docs-only follow-up on this branch. |
Re-reviewNits landed at 7a438c4. README/title: OS-follow; |
TheGreatAxios
left a comment
There was a problem hiding this comment.
Approach is sound. Duplicating the .dark token block inside the media query — with a drift test asserting the two stay token-identical — is the right call given light-dark() would break the contrast gate's hex parsing. The .light escape hatch closes the real hole (explicit light on a dark-OS host), and ThemeProvider's live matchMedia listener means system mode still follows the OS after mount.
Ride-alongs (README install rewrite, sonner store dismissal in toast tests) are unrelated but harmless. Merge.
Native CSS cannot share one declaration list between .dark (any OS) and a media-qualified :root without light-dark(), which would break the contrast gate. Keep the two blocks token-identical, add color-scheme: dark to both, and fail the suite if they drift.
Sonner replays active toasts onto the next Toaster subscriber, so leftover confirmations from a prior case stacked (Expected 1, Received 2).
Removing .dark is not light; hosts need .light. The media-query path sets tokens and color-scheme only — dark: utilities still need a .dark ancestor.
7a438c4 to
9619dad
Compare
Review pass — rebased onto post-prune
|
What
A fresh install with no JS (no
ThemeProvider, no.darkclass ever added)stayed light even on a dark-OS host. Adds a
@media (prefers-color-scheme: dark)block insrc/theme.cssapplying thesame dark tokens to
:rootwhenever neither.darknor an explicit.lightis present.
ThemeProvider(applyThemeToRootinsrc/lib/theme.ts) nowalso toggles a
.lightclass on explicit light mode, so an explicit lightchoice on a dark-OS host is never clawed back by the media query — the
provider still wins in both directions.
dist/theme.cssis generated fromsrc/theme.cssbybun run build(
cp src/theme.css dist/theme.css), so the two stay identical automatically.README: documented the two import paths (
styles.cssvstheme.css), thatskipping both leaves components unstyled (not a fallback look), and the new
zero-JS dark-by-default behavior.
Verified
bun run typecheck— cleanbun run lint— cleanbun run dep-guard— cleanbun test— 337 pass, 0 fail (pre-existing act() warnings, unrelated)bun run build— contrast gate passes all pairs in both light and darkNot verified
host (no zero-JS test harness in this repo for that).