Color mode

This page is part of Themes in Depth and covers color mode.

Color mode

3-1. colorMode

ts
type ThemeColorMode = "light" | "dark" | "system";
Mode Behavior
light Light palette
dark Dark palette
system Follow the OS setting

"system" follows the OS preference. Themes use the data-theme attribute and semantic tokens, and avoid hardcoding colors into individual components.

The server emits data-theme on <html> unless the mode is "system":

html
<html
  data-theme-name="example"
  data-theme="dark"
>

For "system" the attribute is not emitted at all:

html
<html data-theme-name="example">

That difference matters. At runtime the palette is decided by three CSS states.

css
:is(:root, .rb-theme-root)[data-theme-name="<name>"] { /* light */ }
:is(:root, .rb-theme-root)[data-theme-name="<name>"][data-theme="dark"] { /* dark */ }
@media (prefers-color-scheme: dark) {
  :is(:root, .rb-theme-root)[data-theme-name="<name>"]:not([data-theme]) { /* follows the OS (system) */ }
}

The server emits data-theme on <html> unless the theme's colorMode is "system", in which case the attribute is omitted and the media query decides.

Runtime switching contract: set document.documentElement.dataset.theme to "light" or "dark", or remove the attribute for "system":

ts
document.documentElement.dataset.theme = "dark";
ts
delete document.documentElement.dataset.theme;

Do not set data-theme="":

ts
document.documentElement.dataset.theme = "";

An empty attribute still matches [data-theme], so :not([data-theme]) no longer holds and the system media query breaks.

@riebeckite/plugin-color-mode is the reference implementation (see its package README) — an inline ColorModeScript that runs before render, a ColorModeToggle control, and an initColorMode client entry that saves the choice to localStorage.

History

1 changesCollapseExpand
1 + ---
2 + title: Color mode
3 + sidebar:
4 + label: Color mode
5 + order: 10
6 + ---
7 +
8 + This page is part of [Themes in Depth](../theme-system.md) and covers color mode.
9 +
10 + # Color mode
11 +
12 + ## 3-1. colorMode
13 +
14 + ```ts
15 + type ThemeColorMode = "light" | "dark" | "system";
16 + ```
17 +
18 + | Mode | Behavior |
19 + | --- | --- |
20 + | `light` | Light palette |
21 + | `dark` | Dark palette |
22 + | `system` | Follow the OS setting |
23 +
24 + `"system"` follows the OS preference. Themes use the `data-theme` attribute and semantic tokens, and avoid hardcoding colors into individual components.
25 +
26 + The server emits `data-theme` on `<html>` unless the mode is `"system"`:
27 +
28 + ```html
29 + <html
30 + data-theme-name="example"
31 + data-theme="dark"
32 + >
33 + ```
34 +
35 + For `"system"` the attribute is not emitted at all:
36 +
37 + ```html
38 + <html data-theme-name="example">
39 + ```
40 +
41 + That difference matters. At runtime the palette is decided by three CSS states.
42 +
43 + ```css
44 + :is(:root, .rb-theme-root)[data-theme-name="<name>"] { /* light */ }
45 + :is(:root, .rb-theme-root)[data-theme-name="<name>"][data-theme="dark"] { /* dark */ }
46 + @media (prefers-color-scheme: dark) {
47 + :is(:root, .rb-theme-root)[data-theme-name="<name>"]:not([data-theme]) { /* follows the OS (system) */ }
48 + }
49 + ```
50 +
51 + The server emits `data-theme` on `<html>` unless the theme's `colorMode` is `"system"`, in which case the attribute is omitted and the media query decides.
52 +
53 + **Runtime switching contract**: set `document.documentElement.dataset.theme` to `"light"` or `"dark"`, or **remove the attribute** for `"system"`:
54 +
55 + ```ts
56 + document.documentElement.dataset.theme = "dark";
57 + ```
58 +
59 + ```ts
60 + delete document.documentElement.dataset.theme;
61 + ```
62 +
63 + Do not set `data-theme=""`:
64 +
65 + ```ts
66 + document.documentElement.dataset.theme = "";
67 + ```
68 +
69 + An empty attribute still matches `[data-theme]`, so `:not([data-theme])` no longer holds and the system media query breaks.
70 +
71 + `@riebeckite/plugin-color-mode` is the reference implementation (see its package README) — an inline `ColorModeScript` that runs before render, a `ColorModeToggle` control, and an `initColorMode` client entry that saves the choice to `localStorage`.
72 +