SwThemeSwitch

SwThemeSwitch is the built-in theme switcher: driven by useTheme(), clicking cycles between "follow system / light / dark".

Self-gating: the component renders the toggle button only when swift.config.json's theme.mode is 'toggle'; in the system / dark / light modes (which have no manual-switch semantics) it renders nothing. So you just drop <SwThemeSwitch /> into your template — change the theme.mode config and the switcher appears / disappears automatically, no conditional needed.

The switching shape follows theme.toggleStates:

  • 3-state (default): the icon follows theme, and clicking cycles auto → light → dark → auto, so you can return to "follow system".
  • 2-state: the icon follows resolvedTheme (the actual current light/dark), and clicking only toggles light ↔ dark; once switched you can't return to follow-system.

Three customization tiers (headless escape hatch)

From lightest to heaviest:

  1. Reskin — the --sw-theme-switch-* CSS tokens (size / color / background / radius / hover).
  2. Swap icons — the #light / #dark / #auto named slots take icons from your own icon library (only apply to the default single-button UI).
  3. Replace the whole interaction — the default scoped slot: once provided, you self-render the entire UI, and the component hands you { theme, resolvedTheme, mode, toggleStates, toggleTheme, setTheme, canSwitch } while still reusing the self-gate (renders nothing when mode !== 'toggle', so you don't have to check yourself).

If you'd rather build entirely without this component, that works too: useTheme() is a public composable you can read/write the theme with directly.

Basic usage

Default switcher (this docs site is in toggle mode, clicking actually switches the theme)

加载中...
<template>
  <!-- Renders only when theme.mode: 'toggle'; empty in other modes -->
  <SwThemeSwitch />
</template>

Swap icons (named slots)

#light / #dark / #auto override the default inline SVGs with icons from your icon library (scope { theme, resolved }):

Replace default icons with @phosphor-icons/vue

加载中...
<script setup lang="ts">
import { PhSun, PhMoon, PhCircleHalf } from '@phosphor-icons/vue'
</script>

<template>
  <SwThemeSwitch>
    <template #light><PhSun :size="18" /></template>
    <template #dark><PhMoon :size="18" /></template>
    <template #auto><PhCircleHalf :size="18" /></template>
  </SwThemeSwitch>
</template>

Self-render the whole UI (default scoped slot)

Providing the default scoped slot takes over the entire switcher UI while reusing the framework's data + actions + self-gate. Here three buttons form a "follow system / light / dark" single choice:

Self-render via the default scoped slot (setTheme single choice)

加载中...
<template>
  <SwThemeSwitch v-slot="{ theme, resolvedTheme, setTheme }">
    <SwButton :class="{ 'is-active': theme === 'auto' }" @click="setTheme('auto')">Follow system</SwButton>
    <SwButton :class="{ 'is-active': theme === 'light' }" @click="setTheme('light')">Light</SwButton>
    <SwButton :class="{ 'is-active': theme === 'dark' }" @click="setTheme('dark')">Dark</SwButton>
    <span>Resolved: {{ resolvedTheme }}</span>
  </SwThemeSwitch>
</template>

Props

NameTypeRequiredDefaultDescription
sizenumber18Default inline icon size (px). Has no effect when self-rendering via the default scoped slot. ⚠️ Unlike SwTable's size (a 'default' | 'compact' density enum), this one is a pixel number

Slots

NameScopeDescription
default{ theme, resolvedTheme, mode, toggleStates, toggleTheme, setTheme, canSwitch }Self-render the entire switcher UI; takes over when provided, still gated by mode: 'toggle'
light{ theme, resolved }Light-state icon (overrides the default sun; default UI only)
dark{ theme, resolved }Dark-state icon (overrides the default moon; default UI only)
auto{ theme, resolved }Follow-system icon (overrides the default half-circle; 3-state + default UI only)

Skin tokens

TokenPurpose
--sw-theme-switch-sizeButton width/height
--sw-theme-switch-color / --sw-theme-switch-hover-colorIcon color / hover icon color
--sw-theme-switch-bg / --sw-theme-switch-hover-bgBackground / hover background
--sw-theme-switch-radiusCorner radius

Caveats

  • Self-gate controls visibility: the component renders nothing when theme.mode is not toggle — to let users switch themes, set theme.mode: 'toggle' in swift.config.json (see Theme system).
  • size only affects the default inline icons: when using the icon slots or self-rendering via the default scoped slot, the size is up to your own content.
  • No emitted events: switching is driven by the internal toggleTheme; to react to theme changes, watch useTheme()'s theme / resolvedTheme directly.

See Also