Theme toggle
The custom element is <hui-theme-toggle>.
Overview
An optional enhancement that writes the light or dark class on the document root and remembers the choice in localStorage. It is the only element in the library that touches the root element, so a host that pins the theme server-side simply does not load it.
Example
Source
<hui-theme-toggle> <button type="button">Theme: <span data-theme-label>system</span></button></hui-theme-toggle>The Go template that renders it:
<hui-theme-toggle> <button type="button">Theme: <span data-theme-label>system</span></button></hui-theme-toggle>API
Attributes2
| Name | Type | Default | Description |
|---|---|---|---|
storage-key | string | "hui-theme" | The localStorage key the choice is written to and read from. Read on every access, so it may change while the element is connected. |
data-theme | "light" | "dark" | "system" | "system" | Reflected by the element, not authored: the current choice, kept in step with the class on <html>. Read it to drive host styling or other UI. |
Properties1
| Name | Type | Default | Description |
|---|---|---|---|
theme | ThemeChoice | 'system' | Read-only. The stored choice when it is one of the three names, otherwise system. Assign through setTheme(). |
Methods1
| Name | Type | Description |
|---|---|---|
setTheme | (theme: ThemeChoice) => void | Writes the choice to localStorage and applies it to <html>. Unlike a click it does not emit hui-theme-change, so a host calling it is not notified of its own change. |
Events2
| Name | Description |
|---|---|
hui-theme-change | Fired after a click or keypress cycles the theme. It bubbles and is composed, is not cancelable, and carries detail.theme with the new "light" | "dark" | "system" value. setTheme() does not emit it. |
click | The native click from the slotted control, or from the element itself when it takes on role="button". Cycling happens on this event, in the bubble phase. |
Slots1
| Name | Description |
|---|---|
(default) | A server-rendered <button>, <a> or [role="button"], optionally with a [data-theme-label] child that the element keeps in sync with the theme name. When no such control is present the element becomes one itself. |
States
No child control
With no child, the element renders its own button.
Source
<hui-theme-toggle></hui-theme-toggle>Custom storage key
A host's own button as the trigger, data-theme-label filled in with the current theme, and storage-key naming where the preference is kept.
Source
<hui-theme-toggle storage-key="demo-theme"> <button type="button">Theme: <span data-theme-label>system</span></button></hui-theme-toggle>Server-side mechanics
The server owns the theme: it stamps the light or dark class on <html> from the cookie or user setting before first paint, and that class is the theme of record. On connect the element reads the class already present and reflects it as data-theme, so a host that pins the theme server-side simply does not load it - a click would write a class that disagrees with the server, and the element persists its own choice in localStorage. It is not form-associated and submits no value; there is no name. An hx-swap that replaces the markup creates a fresh element, which re-registers its click and keydown listeners and re-reads the stored choice, or the reflected class, on connect.
Accessibility
- The element sets
aria-label="Switch theme, current <theme>"on itself and on a slotted control, so the current choice is part of the control name. - With no slotted control it adds
role="button"andtabindex="0", and handles Enter and Space as a native button would. - The choice is reflected as the
lightordarkclass on<html>and asdata-theme, so host CSS and assistive tech can read the current state. - Storage access is wrapped in a try/catch: a browser that blocks localStorage still toggles for the session, it just cannot persist.
Keyboard
| Keys | Action |
|---|---|
Tab | Moves focus to the slotted control, or to the element when it supplies its own button role. |
Enter, Space | Cycles the theme when the element carries role="button"; otherwise the slotted native control activates as usual. |
Gotchas
- It sets no
aria-pressed. The current choice is exposed througharia-label="Switch theme, current <theme>"and the reflecteddata-theme, so do not look for a pressed state. setTheme()writes storage and<html>but does not emithui-theme-change; only a click or keyboard cycle does, so a host calling it is not notified of its own change.- It consumes no
--hui-*token directly; it only writes thelightordarkclass that the token sheet keys off.