Theme Switcher — Vanilla
Theme switcher with vanilla HTML + JS. Live example and copyable markup.
Theme Switcher
Accessible theme switcher with theme icons and keyboard navigation. Same BEM structure and behavior as the Astro ThemeSwitcher: set data-theme on <html>, persist in localStorage (key theme; use system for OS preference).
Live example
The switcher below is a working example (same component as Astro so it runs on this page). In a vanilla-only project you’d use the same HTML structure and wire it with your own script or theme utilities (applyTheme, getThemeLabel).
HTML (structure)
Copy the markup below. Ensure Rizzo CSS is loaded. Wire trigger/menu open-close and option clicks to your theme utility (e.g. applyTheme(value)).
<div class="theme-switcher" data-theme-switcher>
<button type="button" class="theme-switcher__trigger" aria-expanded="false" aria-haspopup="true" aria-controls="theme-menu" data-theme-trigger>
<span class="theme-switcher__label-wrapper">
<span class="theme-switcher__label" data-theme-label>GitHub Dark Classic</span>
</span>
<svg class="theme-switcher__icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M6 9l6 6 6-6"/></svg>
</button>
<div class="theme-switcher__menu" id="theme-menu" role="menu" hidden>
<div class="theme-switcher__menu-options">
<div class="theme-switcher__group" role="group" aria-label="Preference">
<div class="theme-switcher__group-label">Preference</div>
<button type="button" class="theme-switcher__option" role="menuitemradio" aria-checked="true" data-theme-value="system">System</button>
</div>
<div class="theme-switcher__group" role="group" aria-label="Dark themes">
<div class="theme-switcher__group-label">Dark</div>
<button type="button" class="theme-switcher__option" role="menuitemradio" data-theme-value="github-dark-classic">GitHub Dark Classic</button>
<!-- more dark themes -->
</div>
<div class="theme-switcher__group" role="group" aria-label="Light themes">
<div class="theme-switcher__group-label">Light</div>
<button type="button" class="theme-switcher__option" role="menuitemradio" data-theme-value="github-light">GitHub Light</button>
<!-- more light themes -->
</div>
</div>
<div class="theme-switcher__preview" data-theme-preview aria-hidden="true">
<div class="theme-switcher__preview-title">Preview</div>
<div class="theme-switcher__preview-header" data-theme-preview-label>Theme name</div>
<div class="theme-switcher__preview-swatch-wrap"><div class="theme-switcher__preview-swatch" data-theme-preview-swatch></div></div>
<div class="theme-switcher__preview-accent" data-theme-preview-accent></div>
</div>
</div>
</div> View Theme Switcher on the Astro docs for complete markup, all themes, and preview panel.
Showing a theme icon in Vanilla: Use the same icon SVGs as the switcher; map theme id to icon via themes.ts iconKey (owl, palette, flame, sunset, zap, shield, heart, sun, cake, lemon, rainbow, leaf, cherry, brush). See Icons for SVG assets.
Astro / Svelte: Astro · Svelte (includes ThemeIcon component)