Tutorials

Preference helpers

Three small packages per framework, each owning one user preference end to end — selection, DOM application, persistence. You've met theme-select; here are the other two.

What you'll build

A settings panel where users pick their language and text size — choices that apply to the whole document, persist across visits, and cost you two components and a few lines of CSS. About 15 minutes.

Before you start

Finish the framework tutorial for your stack. The samples below are Svelte; the React, Vue, Angular, HTML, Nunjucks, and Blazor helpers match contract-for-contract.

The shape they share

  • A native <select> with one <option> per choice — native keyboard, mobile OS picker for free.
  • Headless: one class hook (.locale-select, .text-size-select), zero CSS shipped.
  • Optional localStorage persistence via a storageKey prop.
  • SSR-safe: DOM writes happen only in the mount/effect lifecycle.
  • i18n-clean: every user-facing string is a prop.
  • Available for Svelte (canonical), React, Vue, Angular, HTML custom element, Nunjucks, and Blazor — same contract in each.

locale-select — language and direction

Lets the user pick a locale, then writes lang and dir to the document root so your i18n library, your CSS, and assistive technology all follow. RTL is detected automatically for Arabic, Hebrew, and other right-to-left scripts. It focuses purely on signalling — your i18n library handles the translation.

<script>
  import LocaleSelect from "lily-design-system-svelte-locale-select";

  let locale = $state("en-US");
</script>

<LocaleSelect
  label="Language"
  locales={["en-US", "fr", "ar", "he", "ja"]}
  bind:value={locale}
  storageKey="my-app-locale"
/>

Options are labelled from a built-in table of locale names (overridable via localeLabels), each <option> carries its own lang attribute so screen readers pronounce "Français" in French, and underscore codes like en_US round-trip losslessly to BCP 47 en-US. Wire onChange (or the bound value) into i18next, Paraglide, vue-i18n, or whatever you use.

text-size-select — reader-controlled sizing

An accessibility win that takes minutes: the helper sets data-text-size="{slug}" on the document root and your CSS maps each value to sizing.

<script>
  import TextSizeSelect from "lily-design-system-svelte-text-size-select";
</script>

<TextSizeSelect
  label="Text size"
  sizes={["small", "medium", "large", "x-large"]}
  storageKey="my-app-text-size"
/>
:root[data-text-size="small"]   { font-size: 87.5%; }
:root[data-text-size="medium"]  { font-size: 100%; }
:root[data-text-size="large"]   { font-size: 112.5%; }
:root[data-text-size="x-large"] { font-size: 125%; }

Size your layout in rem and the whole app scales with the user's choice. Labels are title-cased from the slugs ("X Large") or overridden with sizeLabels.

Check your work: pick "Large" in the select, and the page text grows. Reload — it's still large: the choice persisted to localStorage. Switch your language select to Arabic and dir="rtl" lands on the document automatically.

A settings panel in one place

The three helpers compose naturally:

<fieldset class="fieldset">
  <legend>Preferences</legend>
  <ThemeSelect    label="Theme"     themesUrl="/themes/" themes={themes} storageKey="pref-theme" />
  <LocaleSelect   label="Language"  locales={locales}    storageKey="pref-locale" />
  <TextSizeSelect label="Text size" sizes={sizes}        storageKey="pref-text-size" />
</fieldset>

Where to get them

git clone https://github.com/LilyDesignSystem/lily-design-system-svelte-helpers
# or -react- / -vue- / -angular- / -html- / -nunjucks- / -blazor-

Each catalog has per-package specs, tests mapped to the spec clauses, runnable examples (including SSR cookie patterns), and a build + publish pipeline. The Svelte catalog is canonical; when in doubt, its contracts win.

Next steps

Questions along the way? The help page is full of answers — and if a step could be clearer, tell us and we'll gladly improve the tutorial.