Skip to content
Fleetline

Theme toggle

Usage

Let people choose how Fleetline looks. Dispatchers on night shift prefer dark; mechanics outdoors usually need light. System is the default and follows the device setting.

Anatomy

  1. Button — .btn.btn--ghost.theme-toggle[data-theme-cycle]
  2. Icons — three svg.theme-toggle__icon[data-for]; only the current one shows
  3. Label — .theme-toggle__label[data-theme-label], updated by js/theme.js
  4. Radio group — fieldset.theme-choice + legend + three label.theme-choice__option wrapping a radio

Examples

The first two demos are live and change this page's theme. The state demos are frozen copies (data-preview on the button, inert radio groups).

Theme toggle

Live: cycles System → Light → Dark; the name says the current theme.

Appearance

Live: settings form. System follows the operating system.

Appearance
Appearance
Appearance
Appearance
<!-- In <head>, before stylesheets: no-flash snippet -->
<script>try{var t=localStorage.getItem("ds-theme");if(t&&t!=="system")document.documentElement.dataset.theme=t}catch(e){}</script>

<!-- Cycling button (header) -->
<button type="button" class="btn btn--ghost theme-toggle" data-theme-cycle data-themes="system light dark">
  <svg class="theme-toggle__icon" data-for="system" aria-hidden="true" focusable="false"><use href="#icon-monitor"></use></svg>
  <svg class="theme-toggle__icon" data-for="light" aria-hidden="true" focusable="false"><use href="#icon-sun"></use></svg>
  <svg class="theme-toggle__icon" data-for="dark" aria-hidden="true" focusable="false"><use href="#icon-moon"></use></svg>
  <span class="theme-toggle__label" data-theme-label>Theme: system</span>
</button>

<!-- Radio group (settings) -->
<fieldset class="theme-choice" data-theme-choice>
  <legend class="theme-choice__legend">Appearance</legend>
  <label class="theme-choice__option"><input type="radio" name="theme" value="system" checked>…System</label>
  <label class="theme-choice__option"><input type="radio" name="theme" value="light">…Light</label>
  <label class="theme-choice__option"><input type="radio" name="theme" value="dark">…Dark</label>
</fieldset>

<script type="module">
  import { initTheme } from "./js/theme.js";
  import { init } from "./js/theme-toggle.js";
  initTheme(); init();
</script>

API

HookValuesPurpose
.theme-toggleblock class on a .btnCycling theme button
data-theme-cycle, data-themesattributeEnhanced by js/theme.js; themes in cycle order
.theme-toggle__icon[data-for]partSystem / light / dark icon
[data-theme-label]attributeText updated to "Theme: …"
.theme-choice[data-theme-choice]block + attributeRadio group, wired by js/theme-toggle.js
.theme-choice__optionpartLabel wrapping a radio
data-previewdocs-only attributeFreeze the icon of a theme
setTheme(), getTheme(), onThemeChange()JS (theme.js)Runtime API

Do and don't

Do say the current theme in the name.

<button aria-label="Dark mode" aria-pressed="false"> on a three-way cycle

Don't use aria-pressed on a button that cycles through three themes — pressed/unpressed can't describe it.

Accessibility

Keyboard interaction
KeyBehavior
Enter / Space (button)Switches to the next theme; the name updates ("Theme: light")
Tab (radio group)Moves into the group, to the checked option
↑ ↓ ← →Select the previous / next theme (native radios)

Tokens

Custom propertyPurpose
--color-action-secondary-*Option rest / hover
--color-selected-bg | -fg | -borderChecked option
--size-control-md, --button-radiusOption size and shape
--icon-size-sm, --icon-strokeIcons
--focus-ring-*Focus ring