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).
Features
- Theme switcher â Integrated ThemeSwitcher; persists as
themein localStorage (e.g.github-dark-classic,system). - Font size slider â 75%â150%; track uses
--slider-progress. Persists asfontSizeScale; apply as--font-size-scaleonhtml. - Reduce motion â Toggle adds
.reduced-motionto document root. Persists asreducedMotion(true/falsestring). - High contrast â Toggle adds
.high-contrastto root. Persists ashighContrast. - Scrollbar style â Radio: Thin (default), Thick, Hidden. Applies
.scrollbar-thickor.scrollbar-hiddenonhtml. Persists asscrollbarStyle(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. Setaria-hiddenfrom 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-valuefor 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", valuesthin/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 orsystem; setdata-themeonhtml.fontSizeScaleâ number string (e.g.1,1.25); setstyle="--font-size-scale: âĻ"onhtml.reducedMotionâ"true"/"false"; add/remove.reduced-motiononhtml.highContrastâ"true"/"false"; add/remove.high-contrastonhtml.scrollbarStyleâthin/thick/hidden; add.scrollbar-thickor.scrollbar-hiddenonhtmlwhen not thin.
Structure example (simplified)
<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). Setaria-hidden={!open}on root, overlay, and panel. ExposeopenSettings()/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
localStorageand updatehtml(classes,data-theme,--font-size-scale). On mount, read fromlocalStorageand set initial state + DOM. - Slider progress: Compute
--slider-progressfrom(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.
// 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.