ThemeSwitch
A three-state segmented control (system / light / dark) wired to ranui's
theme API. Clicking a segment calls setTheme(), persists the choice
under the localStorage key ran-theme, and keeps every instance on the page (and in other
tabs) in sync.
Use when you need a ready-made system/light/dark segmented control wired to ranui's theme API.
<r-theme-switch>handles persistence, system tracking, and cross-tab sync so you don't hand-roll a toggle.
Quick Start
Basic Usage
<r-theme-switch></r-theme-switch>import 'ranui'; // or the standalone entry:
import 'ranui/theme-switch';💡 On this docs site the theme is driven by the site-wide toggle in the header, which overwrites
data-ran-themeon its own, so the demo above may be reset by the site. In your app,<r-theme-switch>is the source of truth.
Call initTheme() once on page load so the saved choice is restored before the switch renders:
import { initTheme } from 'ranui';
initTheme();API Reference
Properties
| Property | Type | Default | Description |
|---|---|---|---|
value |
'system' | 'light' | 'dark' |
'system' |
Current selection, read from the theme API (getTheme()). Setting it applies and persists the theme. |
sheet |
string |
'' |
CSS injected into the component's shadow DOM. |
Localization Attributes
The three buttons are icon-only, so each carries an aria-label. Override them to localize:
| Attribute | Default | Description |
|---|---|---|
label |
'Theme' |
aria-label of the control group. |
label-system |
'System theme' |
aria-label of the system button. |
label-light |
'Light theme' |
aria-label of the light button. |
label-dark |
'Dark theme' |
aria-label of the dark button. |
<r-theme-switch
label="Theme"
label-system="System theme"
label-light="Light theme"
label-dark="Dark theme"
></r-theme-switch>Events
| Event | Detail | Description |
|---|---|---|
change |
{ theme: 'system' | 'light' | 'dark' } |
Fired when the user picks a theme. Bubbles and crosses shadow DOM. |
const themeSwitch = document.createElement('r-theme-switch');
themeSwitch.addEventListener('change', (e) => {
console.log('theme is now', e.detail.theme);
});
toolbar.append(themeSwitch);Behavior
- Persistence: selections go through
setTheme(), so they're saved to localStorage (ran-theme) and restored byinitTheme()on the next visit. - Multi-instance sync: place one switch in the header and another in the footer; picking a theme on either updates both.
- Cross-tab sync: a theme flipped in another tab updates this control via the
storageevent. - Browser chrome: forced light/dark updates
<meta name="theme-color">to the resolved page background so the browser/PWA chrome matches; choosingsystemrestores each meta's original (possibly media-qualified) content.
CSS Parts
| Part | Description |
|---|---|
switch |
The outer segmented pill. |
button |
Every choice button (each also exposes its choice name as an additional part). |
system / light / dark |
The individual choice buttons. |
r-theme-switch::part(switch) {
border-color: var(--line);
}
r-theme-switch::part(dark) {
color: rebeccapurple;
}The following CSS variables can be overridden: --ran-theme-switch-display,
--ran-theme-switch-gap, --ran-theme-switch-padding, --ran-theme-switch-border-color,
--ran-theme-switch-radius, --ran-theme-switch-background, --ran-theme-switch-button-size,
--ran-theme-switch-icon-size, --ran-theme-switch-button-color, --ran-theme-switch-button-hover-color,
--ran-theme-switch-button-active-background, --ran-theme-switch-button-active-color,
--ran-theme-switch-button-focus-outline.
r-theme-switch {
--ran-theme-switch-button-size: 32px;
--ran-theme-switch-icon-size: 18px;
}Best Practices
- One source of truth: use
<r-theme-switch>instead of hand-rolling a toggle; it already handles persistence, system tracking, instance sync, andtheme-colormetas. - Restore early: call
initTheme()as early as possible (ideally inline before first paint) to avoid a light→dark flash. - Localize: the buttons are icon-only; set
label/label-*for non-English UIs.