Settings — Svelte

Settings component — Svelte.

Settings component

Slide-in panel for theme, font size, and accessibility options. All settings persist in localStorage. The docs site uses the Astro Settings in the layout; open via the navbar gear or window.openSettings(). In a Svelte app, use the same BEM classes and wire state + persistence to match.

Live example

Open Settings via the gear icon in the navbar or the button below (same as navbar).

Open Settings

Features

  • Theme switcher — Integrated ThemeSwitcher; persists as theme in localStorage (e.g. github-dark-classic, system).
  • Font size slider — 75%–150%; track uses --slider-progress. Persists as fontSizeScale; apply as --font-size-scale on html.
  • Reduce motion — Toggle adds .reduced-motion to document root. Persists as reducedMotion (true/false string).
  • High contrast — Toggle adds .high-contrast to root. Persists as highContrast.
  • Scrollbar style — Radio: Thin (default), Thick, Hidden. Applies .scrollbar-thick or .scrollbar-hidden on html. Persists as scrollbarStyle (thin, thick, hidden).
  • Slide-in from right with overlay; animations respect prefers-reduced-motion.
  • Focus trap when open; focus returns to trigger on close. Escape closes. ARIA (dialog, labels).
  • Mobile: full-width panel.

Key BEM classes and data attributes

  • settings — root; data-settings. Set aria-hidden from open state.
  • settings__overlay — backdrop; data-settings-overlay; click to close.
  • settings__panel — drawer; role="dialog", aria-modal="true", aria-labelledby="settings-title".
  • settings__header, settings__title, settings__close — header and close button; data-settings-close.
  • settings__content — scrollable body.
  • settings__section, settings__section-title, settings__control — section layout.
  • settings__label, settings__label-text, settings__label-value — labels; data-font-size-value for the percentage display.
  • settings__slider — range input; data-font-size-slider, style="--slider-progress: 50%" for filled track.
  • settings__slider-labels — e.g. Small / Default / Large.
  • settings__checkbox-label, settings__checkbox — toggles; data-reduced-motion, data-high-contrast.
  • settings__radio-group (role="radiogroup"), settings__radio-label, settings__radio — scrollbar options; data-scrollbar-style, name="scrollbar-style", values thin / thick / hidden.
  • settings__help-text — description under controls.

Persistence (localStorage)

Read/write these keys and apply to the document so styles and theme stay in sync:

  • theme — theme id or system; set data-theme on html.
  • fontSizeScale — number string (e.g. 1, 1.25); set style="--font-size-scale: â€Ļ" on html.
  • reducedMotion — "true" / "false"; add/remove .reduced-motion on html.
  • highContrast — "true" / "false"; add/remove .high-contrast on html.
  • scrollbarStyle — thin / thick / hidden; add .scrollbar-thick or .scrollbar-hidden on html when not thin.

Structure example (simplified)

svelte svelte
<div class="settings" data-settings aria-hidden={!open}>
  <div class="settings__overlay" data-settings-overlay onclick={() => (open = false)}></div>
  <div
    class="settings__panel"
    role="dialog"
    aria-modal="true"
    aria-labelledby="settings-title"
    aria-hidden={!open}
  >
    <div class="settings__header">
      <h2 id="settings-title" class="settings__title">Settings</h2>
      <button type="button" class="settings__close" aria-label="Close settings" data-settings-close onclick={() => (open = false)">×</button>
    </div>
    <div class="settings__content">
      <section class="settings__section">
        <h3 class="settings__section-title">Theme</h3>
        <div class="settings__control"><!-- ThemeSwitcher or theme list --></div>
      </section>
      <section class="settings__section">
        <h3 class="settings__section-title">Font Size</h3>
        <div class="settings__control">
          <label for="font-size-slider" class="settings__label">
            <span class="settings__label-text">Adjust text size</span>
            <span class="settings__label-value" data-font-size-value>{fontSizePercent}%</span>
          </label>
          <input type="range" id="font-size-slider" class="settings__slider" min="0.75" max="1.5" step="0.05" bind:value={fontSizeScale} style="--slider-progress: {sliderProgress}" data-font-size-slider />
          <div class="settings__slider-labels"><span>Small</span><span>Default</span><span>Large</span></div>
        </div>
      </section>
      <section class="settings__section">
        <h3 class="settings__section-title">Accessibility</h3>
        <div class="settings__control">
          <label class="settings__checkbox-label">
            <input type="checkbox" class="settings__checkbox" bind:checked={reducedMotion} data-reduced-motion />
            <span>Reduce motion</span>
          </label>
          <p class="settings__help-text">Minimize animations and transitions</p>
        </div>
        <div class="settings__control">
          <label class="settings__checkbox-label">
            <input type="checkbox" class="settings__checkbox" bind:checked={highContrast} data-high-contrast />
            <span>High contrast</span>
          </label>
          <p class="settings__help-text">Increase contrast for better visibility</p>
        </div>
        <div class="settings__control">
          <div class="settings__label"><span class="settings__label-text">Scrollbar style</span></div>
          <div class="settings__radio-group" role="radiogroup" aria-label="Scrollbar style">
            <label class="settings__radio-label">
              <input type="radio" name="scrollbar-style" value="thin" class="settings__radio" bind:group={scrollbarStyle} data-scrollbar-style />
              <span>Thin</span>
            </label>
            <label class="settings__radio-label">
              <input type="radio" name="scrollbar-style" value="thick" class="settings__radio" bind:group={scrollbarStyle} data-scrollbar-style />
              <span>Thick</span>
            </label>
            <label class="settings__radio-label">
              <input type="radio" name="scrollbar-style" value="hidden" class="settings__radio" bind:group={scrollbarStyle} data-scrollbar-style />
              <span>Hidden</span>
            </label>
          </div>
          <p class="settings__help-text">Choose your preferred scrollbar appearance</p>
        </div>
      </section>
    </div>
  </div>
</div>

Implementing in Svelte

  • Open state: e.g. let open = $state(false). Set aria-hidden={!open} on root, overlay, and panel. Expose openSettings() / closeSettings() (e.g. on a global or context) so the navbar can open the panel.
  • Overlay/close: Click overlay or close button → open = false. Escape key → close and restore focus to trigger.
  • Focus trap: When open, focus first focusable in panel; trap Tab inside; on close, focus the element that opened (e.g. settings button).
  • Persistence: On change, write to localStorage and update html (classes, data-theme, --font-size-scale). On mount, read from localStorage and set initial state + DOM.
  • Slider progress: Compute --slider-progress from (value - min) / (max - min) * 100% for the filled track gradient.

Opening from navbar

Expose a global so the navbar settings button can open the panel. In your Settings component, use onMount to set window.openSettings and window.closeSettings to functions that toggle your open state and restore focus. The Vanilla scaffold and Astro layout do the same.

svelte svelte
// Inside your Settings component (or a parent that holds open state)
import { onMount } from 'svelte';
let open = $state(false);
onMount(() => {
  (window as any).openSettings = () => { open = true; };
  (window as any).closeSettings = () => { open = false; };
  return () => {
    delete (window as any).openSettings;
    delete (window as any).closeSettings;
  };
});

Full Astro Settings documentation — layout, persistence, and the inline script you can port to Svelte.

← Back to Svelte components