Every colour on this page comes from a handful of custom properties on the root
element. Switching a theme rewrites those properties, sets data-theme for
CSS-authored palettes, and sets color-scheme, so the form controls, the
caret and the scrollbars below follow along on their own. The choice is stored, and a
snippet in the <head> puts it back before the first paint.
Cards read from --tk-surface and --tk-line; the
progress fill and the tint behind it are the same accent, the tint derived with
color-mix so a new palette needs one value, not two.
Each palette carries its own foreground and muted pair, so text never has to be hand-corrected per theme.
The palettes are declared once and shared between the head snippet and the widget — nothing can drift apart.
A new theme is a name and a handful of values. Nothing in the page markup changes.
These are unstyled-by-default browser widgets. They track the theme because
color-scheme is set on the root — try the select and the checkbox in a light
palette, then in a dark one.
Read live from
getComputedStyle(document.documentElement) — the values above are the
properties the widget just wrote, not a copy of the source data.
A page that reads its theme after the stylesheet has painted shows the wrong
one for a frame or two — the notorious white blink on a dark site. Put this in
<head>, above every stylesheet, and the root element is already
correct when the first pixel lands. It is the version this page runs.