Svelte tutorial
Lily Svelte library na di canonical implementation: Svelte 5 runes, typed props, rest-prop spread, and two-way binding with
bind:value.
Wetin you go build
Small contact form — input wey get label, submit button, two-way binding, real keyboard and screen-reader behaviour — first bare, then styled, for about 10 minutes.
Before you start
You need git, Node 22 or newer, and pnpm 10 or newer. Any Svelte 5 project dey work — fresh npx sv create app go do.
Step 1 — Get di code
git clone https://github.com/LilyDesignSystem/lily-design-system
cd lily-design-system/lily-design-system-svelte-headless
pnpm install
pnpm test # vitest — thousands of component cases
pnpm run storybook # browse all 571 components
Di components dey live inside src/lib/components/{Name}/, one directory for each component wey get di .svelte file, im tests, im stories, and im docs side by side. Copy di directories wey you need enter your app, or depend on di repo and import directly.
Step 2 — Your first component
<script>
import Button from "#lib/components/Button/Button.svelte";
function save() { console.log("saved"); }
</script>
<Button onclick={save}>Save</Button>
E dey render <button class="button" type="button">. Di props dey typed: type, disabled,
pressed (e dey render aria-pressed for toggle buttons), label (an aria-label override), and any extra attribute go spread onto di root.
Step 3 — Compose small form
<script>
import Form from "#lib/components/Form/Form.svelte";
import Field from "#lib/components/Field/Field.svelte";
import TextInput from "#lib/components/TextInput/TextInput.svelte";
import Button from "#lib/components/Button/Button.svelte";
let name = $state("");
</script>
<Form label="Contact form" onsubmit={handleSubmit}>
<Field label="Full name" required>
<TextInput id="name" label="Full name" bind:value={name} required />
</Field>
<Button type="submit">Save</Button>
</Form>
Field dey take di visible label as im own prop instead of nested label component — pass am once, on Field, and TextInputfor label dey provide di accessible name. TextInput's value na
$bindable rune prop, so bind:value dey give you two-way binding straight away.
Verified 2026-08-29: dis exact snippet dem compile through svelte/server and check am for single, correctly linked <label for> — earlier draft nest separate Label inside Field, wey render
two labels (one empty) because Field already dey render im own from di label prop. Di
contact-form composed route
na di same pattern, wey real e2e and axe tests don exercise.
Check your work: run di app and di form go look plain — exactly as dem plan am: di styling still dey come. Press Tab: focus go move through every control, di label go dey announced, and submit dey work. Di behaviour don finish; di look na yours and e be next.
Step 4 — Style am
Everything above dey render as pure structure, ready for your styles. Style di class hooks —
.button, .text-input, .label,
.field — inside your global stylesheet, or link ready-made theme inside app.html:
<link rel="stylesheet" href="/themes/dark.css" />
Per-instance tweaks dey go through di class prop, wey dem append to di base hook: <Button class="wide"> dey render
class="button wide".
Step 5 — See everything dey work
Di SvelteKit example app na di same catalog fully styled, with live demos, Playwright e2e tests, and axe-core accessibility baseline:
git clone https://github.com/LilyDesignSystem/lily-design-system
cd lily-design-system/lily-design-system-svelte-sveltekit-examples
pnpm install && pnpm run dev
Next steps
- Theming — runtime switching with di Svelte theme-picker helper.
- Preference helpers — language and text size.
- Component catalog — props, ARIA, and keyboard for each component.
You get questions as you dey go? Di help page full of answers — and if any step fit clearer, tell us and we go gladly improve di tutorial.