Chooses the light, dark or system color scheme as a switch, an icon button or a segmented group.
<tp-theme-switcher>
Theme switcher
Default density
Compact density
Restart from the initial state
Show the copyable source
Appearance
This card follows the scheme you pick.
Dark mode
Appearance
This card follows the scheme you pick.
Dark mode
<tp-card id="theme-switcher-hero" style="inline-size:22rem;max-inline-size:100%">
<h3 slot="header">Appearance</h3>
<p slot="description">This card follows the scheme you pick.</p>
<div style="display:flex;align-items:center;justify-content:space-between;gap:var(--tp-space-4)">
<span style="font-size:var(--tp-text-sm)">Dark mode</span>
<tp-theme-switcher storage-key="docs-theme-switcher-hero"></tp-theme-switcher>
</div>
</tp-card>
<script type="module">
// Theme only this card, so the rest of the documentation keeps its scheme.
await customElements.whenDefined('tp-theme-switcher');
const card = document.querySelector('#theme-switcher-hero');
card.querySelector('tp-theme-switcher').target = card;
</script>
Usage
The switcher binds a shared preference store: it applies the scheme to its target (the document root by default), persists it under storage-key (tp-theme by default), follows the device while on system, and keeps every switcher on the same target and key in sync. It composes the library’s Switch, Button or Toggle group rather than drawing its own control.
Variants
switch (the default) toggles between explicit light and dark. button cycles dark, light and system from one icon button, and group shows all three as a segmented Toggle group.
Variants
Default density
Compact density
Restart from the initial state
Show the copyable source
Every color role is a light-dark() pair, so popups and portaled content follow the target’s scheme without rewriting tokens. A module script runs after first paint, so apply the stored preference from <head> to avoid a flash of the wrong scheme; the reference docs include the snippet.
light and dark set the target’s inline color-scheme and its data-theme attribute. system sets color-scheme: light dark and removes data-theme, so the device decides, even inside an ancestor that forces one scheme. A change suppresses transitions for one frame so every color switches together, while the switcher’s own thumb and icon motion still plays.
<script>
try {
const value = localStorage.getItem('tp-theme');
if (value === 'light' || value === 'dark') {
document.documentElement.style.colorScheme = value;
document.documentElement.dataset.theme = value;
}
} catch {}
</script>
The same logic is exported as applyColorSchemePreference({ target, storageKey }).
Default and size
default-value applies only when nothing is persisted yet. size="sm" uses the smaller sizes of the composed controls.
Small, defaulting to dark
Default density
Compact density
Restart from the initial state
Show the copyable source
tp-value-change is cancelable: event.detail.value is the proposed preference and detail.metadata.resolved the scheme it resolves to. The composed control’s own change events stay inside the component.
Explicit choices only
Default density
Compact density
Restart from the initial state
Show the copyable source
The preference store behind the switcher is exported as colorSchemeStore(target, { storageKey }). Use it to drive the scheme from any control: set() applies and persists a preference, and subscribe() reports every change, including ones made by switchers on the same target and key. Here a Radio group and a button switcher share one store.
Color scheme store
Default density
Compact density
Restart from the initial state
Show the copyable source
AttributePropertyTypeDefaultDescriptionvariantvariantThemeSwitcherVariant'switch'The control: switch flips between light and dark, button cycles dark, light and system from
one icon button, and group shows all three as a segmented Toggle group.valuevalueColorSchemePreference—The selected preference.default-valuedefaultValueColorSchemePreference | undefined—The preference to start with when nothing is stored yet and the page does not own value.storage-keystorageKeystring | nullDEFAULT_COLOR_SCHEME_STORAGE_KEYLocal storage key; null disables persistence.sizesizeThemeSwitcherSize'default'The size of the composed control; sm uses the small Switch, Button or Toggles.labellabelstring''The accessible name of the control; defaults to "Dark mode" for the switch and "Theme"
otherwise.
Properties
PropertyTypeDefaultDescriptiononValueChange((event: TpValueChangeEvent<ColorSchemePreference>) => void) | undefined—Called with each proposed preference before it applies; assign value to accept it or cancel
the event to reject it.targetHTMLElement | nullnullThe element the preference applies to; the document root by default.resolvedThemeread-onlyResolvedColorScheme—The scheme the preference resolves to on its target.
Events
EventTypeDescriptiontp-value-changeTpValueChangeEvent<ColorSchemePreference>Cancelable proposal of a new preference before it applies; detail.value is light, dark or system, and detail.metadata.resolved the scheme it resolves to.