Cookbook / Theme Toggle

Theme Toggle

display

Theme switcher — state-backed theme is provided to descendants and mirrored onto documentElement by an effect.

id
theme-toggle
since
0.5.0
file
theme-toggle.aihu
stateprovideeffectactionon:clickinterpolation

Run it

source · static

This recipe doesn't have a hydrated island in the demo gallery yet. An in-browser, WASM-compiled playground (edit this source and re-render live) is planned as a follow-up — read the real source below.

Source

theme-toggle.aihu
governedtheme-toggle.aihuaihu1.2 kB
// theme-toggle — dark/light toggle: provide() for theme + effect() for documentElement class

@state {
  let theme = state('light')

  provide('theme', theme)

  effect(() => {
    document.documentElement.classList.toggle('dark', theme === 'dark')
    document.documentElement.classList.toggle('light', theme === 'light')
  })

  const toggle = action(() => { theme = theme === 'light' ? 'dark' : 'light' })
  const setTheme = action((t: 'light' | 'dark') => { theme = t })
}

@template {
  <div class="theme-toggle">
    <button
      class={['toggle-btn', theme]}
      on:click={toggle}
      aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} theme`}
    >
      {theme === 'light' ? '☀ Light' : '☾ Dark'}
    </button>
    <span class="current">Current: {theme}</span>
  </div>
}

@style {
  .theme-toggle { display: flex; align-items: center; gap: 1rem; padding: 0.75rem 1rem; }
  .toggle-btn { padding: 0.4rem 0.9rem; border: 1px solid var(--border, #ccc); border-radius: 20px; cursor: pointer; font-size: 0.9rem; transition: background 0.2s; }
  .toggle-btn.light { background: #fff; color: #333; }
  .toggle-btn.dark { background: #111; color: #eee; border-color: #555; }
  .current { font-size: 0.8rem; color: var(--muted, #888); }
}

Requires

concerns
stylingstate

Anti-patterns

  • Do not toggle document-level classes inside action bodies — the effect() re-runs whenever theme changes, keeping DOM and state in sync from one place.

Related recipes