# Dark mode Dark mode is built into the default theme. You don't build a second stylesheet or add `dark:` variants to your markup. The semantic tokens flip, and everything built from them follows. ## How it works `themes/default.css` defines the light semantic tokens on `:root`, then remaps the same tokens for dark mode. The ramps don't move; the semantic pointers do. `--bg` goes from white to `--neutral-950`, `--ink` from `--neutral-900` to `--neutral-50`, and so on for every surface, ink, rule, accent, and status token. Because components never check the mode (they only read semantic tokens), anything you build from tokens is dark-ready for free. A card that uses `var(--bg)` and `var(--ink)` is correct in both modes with no extra rules. ## OS preference by default Out of the box the app follows the operating system's preference through `@media (prefers-color-scheme: dark)`. A user whose OS is in dark mode sees your app in dark mode with no configuration. ## Forcing a mode with data-theme To let users override the OS preference, set a `data-theme` attribute on ``: - `data-theme="dark"` forces dark mode. - `data-theme="light"` forces light mode (it also suppresses the OS dark preference). The attribute always wins over the OS setting. Wire it to a user toggle (whatever JavaScript your app uses to flip the attribute) and persist the choice. The dark token set is defined twice in `themes/default.css` (once in the media query, once under the attribute) and the two blocks are kept identical. ```html ``` ## Writing dark-ready CSS Consume semantic tokens and you never write a dark-mode rule: ```css /* Correct in both modes automatically */ .panel { background: var(--bg-panel); color: var(--ink); border: 1px solid var(--rule); } ``` Reaching past the semantic tokens to a raw ramp step (`--neutral-50`) opts that value out of the flip, so it stays light in dark mode. Stick to `--bg*`, `--ink*`, `--rule*`, `--accent*`, and the status tokens. ## Exception: email The email baseline is exempt. Mail clients can't reliably resolve CSS variables, so `email.css` ships raw values and is light-only. See [email](../email). ## color-scheme The theme also sets the CSS `color-scheme` property (`light` on `:root`, `dark` in the dark blocks), so native form controls, scrollbars, and the browser chrome match the active mode.