fix(ui): stop body data-theme from shadowing theme preset tokens

applyTheme() mirrors the active mode onto <body> as data-theme="dark", but
the theme preset is only ever written to <html>. The palette token blocks in
tokens/colors.css and base.css used the bare attribute selector
[data-theme="dark"], so <body> matched them on its own and re-declared the
default dark palette (#1a1a1a / #2d2d2d / ...) directly on the body, where it
shadowed the preset values inherited from <html>. Every non-default preset
therefore painted the selected accent over the default preset's background,
surface, text and border tokens, and flipped into that state ~200ms after
load, when initTheme() first touched <body> — the accent-tinted background
flash seen on reload and nav-tab switches. Reached only in dark mode, since
light mode has no [data-theme="light"] token block.

Scope the palette token blocks to :root so a data-theme attribute on any
descendant (only <body> has one) can no longer re-declare them; descendant
rules such as [data-theme="dark"] .foo still match through <html>. Add a
regression guard that fails on bare attribute token blocks.
This commit is contained in:
Will Miao
2026-09-27 21:48:58 +08:00
parent 0a262cbe0c
commit 0dd8d74032
4 changed files with 81 additions and 13 deletions
+1 -1
View File
@@ -572,7 +572,7 @@
}
/* Dark mode: use each preset's dark-mode accent lightness for visibility.
These match the --color-accent-l values from [data-theme="dark"][data-theme-preset="..."]
These match the --color-accent-l values from :root[data-theme="dark"][data-theme-preset="..."]
in tokens/colors.css so the swatch accurately previews what the theme looks like. */
[data-theme="dark"] .preset-swatch-default {