Mode Toggle

A light/dark switch that persists the choice, avoids a flash on load and can follow the system.

Drop it in for a light/dark toggle with none of the usual boilerplate: it writes the dark class on <html>, remembers the choice in localStorage, and reacts to the OS when set to follow the system. Tailwind's dark mode reads that dark class, so the whole page (and every kit component) re-themes.

<c-ui.mode-toggle />
{% cotton ui.mode-toggle /%}

Switch style

Prefer a toggle that reads as a switch? Set variant="switch" for a sliding pill whose thumb carries the current icon. Same engine underneath, so it stays in sync with every other control on the page.

<c-ui.mode-toggle variant="switch" />
{% cotton ui.mode-toggle variant="switch" /%}

Default scheme

Set default to control what a first-time visitor (no stored choice) sees. "system" follows the OS; use "dark" or "light" to force a starting scheme instead. Keep it in sync with the head snippet.

<c-ui.mode-toggle.head default="dark" />

<c-ui.mode-toggle variant="menu" default="dark" />
{% cotton ui.mode-toggle.head default="dark" /%}

{% cotton ui.mode-toggle variant="menu" default="dark" /%}

Custom icons

The button ships inline sun/moon icons. Override them with the light and dark slots (and system_icon for the menu's System row). Pass any SVG, or pull a ready-made icon from a set like cotton-icons (<c-heroicon.sun />).

{# any SVG, or an icon component from cotton-icons #}
<c-ui.mode-toggle>
    <c-slot name="light"><c-heroicon.sun class="size-5" /></c-slot>
    <c-slot name="dark"><c-heroicon.moon class="size-5" /></c-slot>
</c-ui.mode-toggle>
{# any SVG, or an icon component from cotton-icons #}
{% cotton ui.mode-toggle %}
    {% cotton:slot light %}{% cotton heroicon.sun class="size-5" /%}{% endcotton:slot %}
    {% cotton:slot dark %}{% cotton heroicon.moon class="size-5" /%}{% endcotton:slot %}
{% endcotton %}

Bring your own UI

Need a structure the built-in variants don't cover? Use variant="headless": the component renders only the state engine and drops your markup inside its scope. Any element in the slot can call set('light'), set('dark'), set('system') or toggle(), and read mode and isDark for styling. Persistence, the dark class and cross-control sync all still work. The example below rebuilds the menu variant by hand to show the moving parts.

<c-ui.mode-toggle variant="headless">
    {# your markup; set()/mode/isDark come from the component #}
    <div x-data="{ open: false }" @click.outside="open = false" class="relative">
        <button @click="open = !open">Theme</button>
        <div x-show="open" x-cloak>
            <button @click="set('light'); open = false"
                    :class="mode === 'light' && 'font-medium'">Light</button>
            <button @click="set('dark'); open = false"
                    :class="mode === 'dark' && 'font-medium'">Dark</button>
            <button @click="set('system'); open = false"
                    :class="mode === 'system' && 'font-medium'">Auto</button>
        </div>
    </div>
</c-ui.mode-toggle>
{% cotton ui.mode-toggle variant="headless" %}
    {# your markup; set()/mode/isDark come from the component #}
    <div x-data="{ open: false }" @click.outside="open = false" class="relative">
        <button @click="open = !open">Theme</button>
        <div x-show="open" x-cloak>
            <button @click="set('light'); open = false"
                    :class="mode === 'light' && 'font-medium'">Light</button>
            <button @click="set('dark'); open = false"
                    :class="mode === 'dark' && 'font-medium'">Dark</button>
            <button @click="set('system'); open = false"
                    :class="mode === 'system' && 'font-medium'">Auto</button>
        </div>
    </div>
{% endcotton %}

Reference

Props

Name Description Type Options Default
variant button: two-way icon button; switch: sliding pill; menu: light/dark/system dropdown; headless: render only the engine and supply your own UI in the slot str
buttonswitchmenuheadless
button
:system Include the System row in the menu variant (ignored by button and switch) bool
TrueFalse
True
default Scheme used before the visitor makes a choice; 'system' follows the OS str
systemdarklight
system
storage_key localStorage key the choice is saved under (match the head snippet) str theme
size Control size (button and menu variants) str
smmdlg
md
class Extra classes on the control str

Slots

Name Description Type Options Default
light Icon shown in light mode (defaults to a sun) slot
dark Icon shown in dark mode (defaults to a moon) slot
system_icon Icon for the System row in the menu variant slot
Theme
Accent
Ink