Tutorials

Preference helpers

Five small packages per framework, each owning one user job end to end. You've met theme-picker; here are locale-picker and text-size-picker (preferences), plus share-picker and date-time-picker (actions and form values).

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 headless icon button that opens a WAI-ARIA APG listbox (or, for date-time-picker, a date-picker dialog) — no native <select>, so every framework gets identical keyboard behaviour and styling hooks.
  • One class hook per package (.locale-picker, .text-size-picker, .share-picker, .date-time-picker), zero CSS shipped.
  • 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.

theme-picker, locale-picker, and text-size-picker each own one user preference end to end (selection, DOM application, optional localStorage persistence via a storageKey prop). share-picker and date-time-picker are different in kind: share-picker owns an action — it applies nothing to the document and persists nothing — and date-time-picker owns a form value, since a date in a form is data rather than a preference.

locale-picker — 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 LocalePicker from "lily-design-system-svelte-locale-picker";

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

<LocalePicker
  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-picker — 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 TextSizePicker from "lily-design-system-svelte-text-size-picker";
</script>

<TextSizePicker
  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" from the picker, and the page text grows. Reload — it's still large: the choice persisted to localStorage. Switch your language picker to Arabic and dir="rtl" lands on the document automatically.

share-picker — send this page

A single-glyph button that opens the device's native share sheet where one exists, and otherwise falls back to a list of destinations you supply, plus a copy-the-URL action. It ships no social-network URLs — which networks belong in your product is your call, not the design system's.

<script>
  import SharePicker from "lily-design-system-svelte-share-picker";

  const targets = [
    { id: "mastodon", label: "Mastodon",
      href: (url, title) => `https://mastodon.social/share?text=${encodeURIComponent(title)}%20${encodeURIComponent(url)}` },
    { id: "email", label: "Email",
      href: (url, title) => `mailto:?subject=${encodeURIComponent(title)}&body=${encodeURIComponent(url)}`, newTab: false },
  ];
</script>

<SharePicker
  label="Share this page"
  title="An article worth reading"
  {targets}
  copyLabel="Copy link"
  copiedLabel="Link copied"
/>

url defaults to location.href, so the common case needs no wiring. Force a path with strategy="list" or strategy="native" when you need consistent behaviour across platforms for testing or screenshots.

date-time-picker — a date, a time, or both

A text field you can type into, plus an icon button that opens a WAI-ARIA APG date-picker dialog. Locale-correct by construction — month names, weekday names, first day of week, and 12- vs 24-hour clock all come from Intl — and the value is always a sortable, timezone-free ISO string: "2026-03-15" for mode="date", "09:30" for mode="time", "2026-03-15T09:30" for mode="datetime".

<script>
  import DateTimePicker from "lily-design-system-svelte-date-time-picker";

  let appointment = $state("");
</script>

<DateTimePicker
  inputId="appointment"
  name="appointment"
  label="Choose an appointment date"
  locale="en-GB"
  bind:value={appointment}
  labels={{
    previousYear: "Previous year",
    previousMonth: "Previous month",
    nextMonth: "Next month",
    nextYear: "Next year",
    confirm: "OK",
    cancel: "Cancel",
  }}
/>

The ISO value is identical to what <input type="date"> posts, so you can swap the native control in or out without touching your backend.

A settings panel in one place

The three preference helpers compose naturally:

<fieldset class="fieldset">
  <legend>Preferences</legend>
  <ThemePicker    label="Theme"     themesUrl="/themes/" themes={themes} storageKey="pref-theme" />
  <LocalePicker   label="Language"  locales={locales}    storageKey="pref-locale" />
  <TextSizePicker 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.