Theme switcher

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

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

These three share a target and a storage key, so changing one updates the others.

The whole page

Without a target, the switcher themes the document root. This one shares the default store with the switcher in the site header, so they stay in step.

Page theme
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
Editor theme

Vetoing a change

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
Workspace theme: light

Your own control

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
Appearance Light Dark Match system

API Reference

Every element also accepts motion-policy.

Attributes

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.