Skip to content

ThemeToggle

import { ThemeToggle } from '@delightstack/components';
View code
<ThemeToggle />

Bind to the theme prop to read or control the current theme preference.

<script>
import { ThemeToggle } from '@delightstack/components';
let theme = $state<'light' | 'dark' | 'auto'>('auto');
</script>
<ThemeToggle bind:theme />
<p>Current theme: {theme}</p>

Disable the system/auto option to cycle only between light and dark.

View code
<ThemeToggle disable_auto />

Set show_label to display the current mode name (“Light” / “Dark” / “Auto”) beside the icon. Pass label to override the text with your own.

View code
<ThemeToggle show_label />
<ThemeToggle show_label label="Appearance" />

Bind to is_dark to get the resolved dark state (taking system preference into account when theme is 'auto').

<script>
import { ThemeToggle } from '@delightstack/components';
let theme = $state<'light' | 'dark' | 'auto'>('auto');
let is_dark = $state(false);
</script>
<ThemeToggle bind:theme bind:is_dark />
{#if is_dark}
<p>Dark mode is active</p>
{:else}
<p>Light mode is active</p>
{/if}

Use a smaller size to fit in navigation bars.

<script>
import { ThemeToggle } from '@delightstack/components';
</script>
<header>
<nav><!-- nav items --></nav>
<ThemeToggle size="0" />
</header>

Use the onchange callback to run logic when the theme changes.

<ThemeToggle
onchange={(newTheme) => {
console.log('Theme changed to:', newTheme);
}}
/>

Override the default tooltip text.

View code
<ThemeToggle tooltip="Change appearance" />
PropTypeDefaultDescription
theme'light' | 'dark' | 'auto''auto'Current theme preference ($bindable())
is_darkbooleanfalseComputed resolved dark state ($bindable(), read-only). True when effective appearance is dark.
size'0' | '1' | '2''1'Component size ('0' = 18px icon, '1' = 24px icon, '2' = 32px icon)
disable_autobooleanfalseHide the auto/system option (two-state toggle only)
show_labelbooleanfalseShow a text label beside the icon (“Light” / “Dark” / “Auto”)
labelstringundefinedCustom label text override. Falls back to the current mode name when show_label is set.
tooltipstring''Override the default tooltip text. Defaults to the aria-label value.
idstringundefinedElement ID
classstring''Additional CSS classes
onchange(theme: 'light' | 'dark' | 'auto') => voidundefinedCalled when the theme changes
EventDetailDescription
onchange'light' | 'dark' | 'auto'Fires after cycling to a new theme state
ModeCycle
Three-state (default)light -> dark -> auto -> light
Two-state (disable_auto)light -> dark -> light

When the theme changes, the component:

  1. Sets document.documentElement.style.colorScheme to 'light' or 'dark' based on the effective dark state.
  2. Saves the preference to localStorage under the key 'delightstack:theme'.
  3. Watches for system preference changes via matchMedia('(prefers-color-scheme: dark)') and updates is_dark reactively when theme is 'auto'.

To prevent a flash of wrong theme on page load, add the following inline script to your HTML <head> (runs synchronously before the body renders):

<head>
<script>
(function() {
try {
var saved = localStorage.getItem('delightstack:theme');
var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
var theme = (saved === 'light' || saved === 'dark' || saved === 'auto') ? saved : 'auto';
var is_dark = theme === 'dark' || (theme === 'auto' && prefersDark);
document.documentElement.style.colorScheme = is_dark ? 'dark' : 'light';
} catch(e) {}
})();
</script>
</head>
  • Renders as a <button> element with a descriptive aria-label (e.g., “Theme: dark”, “Theme: auto (system preference)”)
  • Keyboard toggle via Space and Enter keys
  • Focus ring on :focus-visible
  • Tooltip via {@attach tooltip()} showing the current state
  • The SVG icon uses aria-hidden="true" since the button’s aria-label conveys the meaning
  • When theme is 'auto', a small “A” indicator appears on the button