app-color-mode
The light / dark / system switcher. It writes the choice to a mode cookie, toggles
the dark class and color-scheme on
<html>, and follows the operating system while the choice is
system. Because the choice is a cookie rather than browser storage, the server
already knows it on the next request and ships the correct theme in the first paint.
Quick start
Drop the element into a header. It needs no attributes and no wiring — everything on this page, including the page you are reading, re-themes when you use it.
<app-color-mode></app-color-mode>
Events
The widget dispatches nothing. It is a control over global document state, so the state itself
is the interface: watch the dark class on <html> with a
MutationObserver
if you need to react in JavaScript, and read
$device.mode
on the next request if you need to react on the server.
| What changes | Where | Detail |
|---|---|---|
| mode | cookie |
path=/, max-age=31536000 (one year),
SameSite=Lax. Read back server-side as $device.mode.
|
| class="dark" | <html> |
Added when the effective theme is dark. This is the class every
dark:
utility keys off.
|
| style.colorScheme | <html> |
Set to light or dark so native controls, scrollbars and form
widgets follow.
|
The server already knows
The choice is a cookie, so the very next request carries it and the server resolves the theme
before rendering. That is what $device.mode and
$device.effective
hold, and it is why this page's markup arrives already themed rather than flashing light and
correcting itself. Both values are live below — change the mode above and reload.
- $device.mode
- light
- $device.effective
- light
| Cookie | $device.mode | $device.effective |
|---|---|---|
| dark | dark | dark |
| light | light | light |
| system | system |
the Sec-CH-Prefers-Color-Scheme hint, else light — and the
document corrects it before it paints
|
| absent / invalid | light | light |
The client hint only arrives on Chromium browsers, so under system the server's
answer is a guess everywhere else. Nothing is recorded to paper over that — the document
resolves system in its own head, before the first paint, which is earlier than any
note about last time could be read and right on every browser. An explicit
light
or dark needs none of this: it is the visitor's own decision and the server already
has it.
Styling against it
Everything downstream is the ordinary dark: variant — the widget's only job is
putting the class on the root. Because the class is already on the server-rendered
<html>, the dark styles apply to the first paint too.
bg-neutral-100 dark:bg-neutral-800
Surface
border-neutral-200 dark:border-neutral-700
Border
bg-primary-600
Brand colours are mode-independent
Usage guidance
Place one instance in the application shell, not one per page — every copy drives the same
document root and the same cookie, so several on screen simply mirror each other (as the three
on this page do). Prefer the drop over toggle unless space is tight:
system
is the setting most people want, and the toggle cannot reach it. Do not write the
mode
cookie yourself from application code; let the widget own it so the document class, the
color-scheme
and the cookie can never disagree.