Color mode

This page is part of Themes in Depth and covers the theme root and theme attributes.

Theme root and attributes

3-6. Theme root selector

Built-in themes do not target the bare :root. Every rule is scoped to the theme's identity name so the same stylesheet can style the real document and an embedded preview:

css
:is(:root, .rb-theme-root)[data-theme-name="<name>"]

<name> is the theme's identity name: riebeckite for the default theme, otherwise one of minimal, gruvbox, sakura, tokyonight, rerurate.

  • On a real site the app sets data-theme-name on <html>, so the :root branch matches the document root.
  • In a preview such as a theme gallery, the same stylesheet styles any element carrying class="rb-theme-root" data-theme-name="<name>", so several themes can render side by side in one document.
Diagram source
text
flowchart TD
    CSS["Same theme CSS"]
 
    CSS --> Site["Real site<br/>:root"]
    CSS --> PreviewA["Preview<br/>.rb-theme-root"]
    CSS --> PreviewB["Another theme preview<br/>.rb-theme-root"]

Never define the theme CSS against a bare :root:

css
:root {
  /* ... */
}

Light, dark, system, typography, theme options, and element/pseudo rules all carry the same prefix:

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]) { /* system */ }
}
:is(:root, .rb-theme-root)[data-theme-name="<name>"][data-typography="serif"] { /* typography */ }
:is(:root, .rb-theme-root)[data-theme-name="<name>"][data-tokyonight-neon="on"] { /* theme option */ }
:is(:root, .rb-theme-root)[data-theme-name="<name>"] :focus-visible { /* element/pseudo */ }

The app provides data-theme-name; a preview container only needs the .rb-theme-root hook and the matching name.

4. Color mode, attributes, and options in detail

Beyond the contract in 3-1, theme-specific options can be passed to CSS through safe data-* attributes.

ts
return defineTheme({
  name: "newspaper",
  attributes: {
    "data-newspaper-density": "compact",
  },
});

The attribute can then be targeted from CSS:

css
:is(:root, .rb-theme-root)[data-theme-name="newspaper"][data-newspaper-density="compact"] {
  /* ... */
}

The theme API is not designed to modify class, style, id, or lang freely. It keeps the namespace of framework-owned attributes separate from theme-specific ones.

Theme Root and themeRootAttributes

The framework provides ThemeRoot, a UI primitive that handles setting theme attributes on the <html> element.

tsx
import { ThemeRoot } from "@riebeckite/honox/ui";
 
<ThemeRoot
  theme={config.theme}
  lang={c.get("htmlLanguage") ?? config.site.locale}
>
  {children}
</ThemeRoot>

ThemeRoot renders the <html> element with the following attributes:

html
<html
  lang="en"
  data-theme-name="minimal"
  data-theme="dark"
  data-typography="system"
  data-article-layout="article"
>

The framework uses themeRootAttributes(theme) to emit the theme's own attributes plus reserved attributes:

  • data-theme: Color Mode state ("light" / "dark" / omitted for "system")
  • data-theme-name: Theme identity name
  • data-typography: Typography preset value
  • data-article-layout: Article layout preset value

To add your own <html> attributes, use the themeRootAttributes helper directly instead of ThemeRoot:

tsx
import { themeRootAttributes } from "@riebeckite/honox/ui";
 
<html
  lang={c.get("htmlLanguage") ?? config.site.locale}
  {...themeRootAttributes(config.theme)}
  data-custom-attr="..."
>
  ...
</html>

However, the framework-reserved data-theme, data-theme-name, data-typography, and data-article-layout cannot be overwritten by the theme's attributes.

If a plugin or theme previously implemented its own themeAttributes(), consider migrating to the framework-provided ThemeRoot / themeRootAttributes(). This clarifies the separation between framework-owned and theme-specific attribute namespaces.

History

1 changesCollapseExpand
1 + ---
2 + title: Theme root and attributes
3 + sidebar:
4 + label: Theme root and attributes
5 + order: 40
6 + ---
7 +
8 + This page is part of [Themes in Depth](../theme-system.md) and covers the theme root and theme attributes.
9 +
10 + # Theme root and attributes
11 +
12 + ## 3-6. Theme root selector
13 +
14 + Built-in themes do not target the bare `:root`. Every rule is scoped to the
15 + theme's identity name so the same stylesheet can style the real document and
16 + an embedded preview:
17 +
18 + ```css
19 + :is(:root, .rb-theme-root)[data-theme-name="<name>"]
20 + ```
21 +
22 + `<name>` is the theme's identity name: `riebeckite` for the default theme,
23 + otherwise one of `minimal`, `gruvbox`, `sakura`, `tokyonight`, `rerurate`.
24 +
25 + - On a real site the app sets `data-theme-name` on `<html>`, so the `:root`
26 + branch matches the document root.
27 + - In a preview such as a theme gallery, the same stylesheet styles any
28 + element carrying `class="rb-theme-root" data-theme-name="<name>"`, so
29 + several themes can render side by side in one document.
30 +
31 + ```mermaid
32 + flowchart TD
33 + CSS["Same theme CSS"]
34 +
35 + CSS --> Site["Real site<br/>:root"]
36 + CSS --> PreviewA["Preview<br/>.rb-theme-root"]
37 + CSS --> PreviewB["Another theme preview<br/>.rb-theme-root"]
38 + ```
39 +
40 + Never define the theme CSS against a bare `:root`:
41 +
42 + ```css
43 + :root {
44 + /* ... */
45 + }
46 + ```
47 +
48 + Light, dark, system, typography, theme options, and element/pseudo rules all
49 + carry the same prefix:
50 +
51 + ```css
52 + :is(:root, .rb-theme-root)[data-theme-name="<name>"] { /* light */ }
53 + :is(:root, .rb-theme-root)[data-theme-name="<name>"][data-theme="dark"] { /* dark */ }
54 + @media (prefers-color-scheme: dark) {
55 + :is(:root, .rb-theme-root)[data-theme-name="<name>"]:not([data-theme]) { /* system */ }
56 + }
57 + :is(:root, .rb-theme-root)[data-theme-name="<name>"][data-typography="serif"] { /* typography */ }
58 + :is(:root, .rb-theme-root)[data-theme-name="<name>"][data-tokyonight-neon="on"] { /* theme option */ }
59 + :is(:root, .rb-theme-root)[data-theme-name="<name>"] :focus-visible { /* element/pseudo */ }
60 + ```
61 +
62 + The app provides `data-theme-name`; a preview container only needs the
63 + `.rb-theme-root` hook and the matching name.
64 +
65 + ## 4. Color mode, attributes, and options in detail
66 +
67 + Beyond the contract in 3-1, theme-specific options can be passed to CSS through safe `data-*` attributes.
68 +
69 + ```ts
70 + return defineTheme({
71 + name: "newspaper",
72 + attributes: {
73 + "data-newspaper-density": "compact",
74 + },
75 + });
76 + ```
77 +
78 + The attribute can then be targeted from CSS:
79 +
80 + ```css
81 + :is(:root, .rb-theme-root)[data-theme-name="newspaper"][data-newspaper-density="compact"] {
82 + /* ... */
83 + }
84 + ```
85 +
86 + The theme API is not designed to modify `class`, `style`, `id`, or `lang` freely. It keeps the namespace of framework-owned attributes separate from theme-specific ones.
87 +
88 + ## Theme Root and themeRootAttributes
89 +
90 + The framework provides `ThemeRoot`, a UI primitive that handles setting theme attributes on the `<html>` element.
91 +
92 + ```tsx
93 + import { ThemeRoot } from "@riebeckite/honox/ui";
94 +
95 + <ThemeRoot
96 + theme={config.theme}
97 + lang={c.get("htmlLanguage") ?? config.site.locale}
98 + >
99 + {children}
100 + </ThemeRoot>
101 + ```
102 +
103 + `ThemeRoot` renders the `<html>` element with the following attributes:
104 +
105 + ```html
106 + <html
107 + lang="en"
108 + data-theme-name="minimal"
109 + data-theme="dark"
110 + data-typography="system"
111 + data-article-layout="article"
112 + >
113 + ```
114 +
115 + The framework uses `themeRootAttributes(theme)` to emit the theme's own `attributes` plus reserved attributes:
116 +
117 + - `data-theme`: Color Mode state (`"light"` / `"dark"` / omitted for "system")
118 + - `data-theme-name`: Theme identity name
119 + - `data-typography`: Typography preset value
120 + - `data-article-layout`: Article layout preset value
121 +
122 + To add your own `<html>` attributes, use the `themeRootAttributes` helper directly instead of `ThemeRoot`:
123 +
124 + ```tsx
125 + import { themeRootAttributes } from "@riebeckite/honox/ui";
126 +
127 + <html
128 + lang={c.get("htmlLanguage") ?? config.site.locale}
129 + {...themeRootAttributes(config.theme)}
130 + data-custom-attr="..."
131 + >
132 + ...
133 + </html>
134 + ```
135 +
136 + However, the framework-reserved `data-theme`, `data-theme-name`, `data-typography`, and `data-article-layout` cannot be overwritten by the theme's `attributes`.
137 +
138 + If a plugin or theme previously implemented its own `themeAttributes()`, consider migrating to the framework-provided `ThemeRoot` / `themeRootAttributes()`. This clarifies the separation between framework-owned and theme-specific attribute namespaces.
139 +