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
- Component catalog — the full 491-component reference.
- Help — troubleshooting and FAQ.
- Why Lily — and how to support the project.
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.