The four theme blocks, live
Four blocks, three reader states
A reader is in one of three states, not two: no preference stamped, in which case the operating system decides; an explicit light choice; an explicit dark choice. Serving all three takes four blocks, and the site's theme toggle stamps data-theme on <html> to reach the last two.
:root /* the complete LIGHT palette */
@media (prefers-color-scheme: dark) :root /* OS dark, for the un-stamped case */
:root[data-theme='light'] /* explicit light, over OS dark */
:root[data-theme='dark'] /* explicit dark, over OS light */Order and specificity both carry weight. A media query adds no specificity, so OS dark beats the base on source order alone and has to be written after it. Both stamped blocks carry an attribute, so they beat OS dark whatever the order, which is what lets a reader's choice win back over the operating system. The base block stays at plain :root: anything that outranks it would take a consumer's own :root declaration out of the cascade for no gain.
The demo
The same content under all four blocks, drawn from the measured palette. Pick a reader state; the panel whose block the cascade resolves to carries the ring. To see the override, pick the explicit state your operating system disagrees with: the stamped panel is active and the OS panel is not.
:rootUnstamped, so the operating system decides. The base block is the complete light palette.
@media (prefers-color-scheme: dark) :rootOS dark, for the unstamped case. A media query adds no specificity, so it beats the base on source order alone.
:root[data-theme='light']Explicit light. Carries an attribute, so it outranks OS dark whatever the order: the reader wins back over the operating system.
:root[data-theme='dark']Explicit dark, over OS light. The mirror of the panel above: an explicit stamped state always beats the OS preference when they conflict.