Tutorials

Svelte tutorial

Lily's Svelte library is the canonical implementation: Svelte 5 runes, typed props, rest-prop spread, and two-way binding with bind:value.

What you'll build

A small contact form — labelled input, submit button, two-way binding, real keyboard and screen-reader behaviour — first bare, then styled, in about 10 minutes.

Before you start

You need git, Node 22 or later, and pnpm 10 or later. Any Svelte 5 project works — a fresh npx sv create app is fine.

Step 1 — Get the code

git clone https://github.com/LilyDesignSystem/lily-design-system-svelte-headless
cd lily-design-system-svelte-headless
pnpm install
pnpm test        # vitest — thousands of component cases
pnpm run storybook   # browse all 490 components

Components live in src/lib/components/{Name}/, one directory per component with the .svelte file, its tests, its stories, and its docs side by side. Copy the directories you need into your app, or depend on the 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>

Renders <button class="button" type="button">. The props are typed: type, disabled, pressed (renders aria-pressed for toggle buttons), label (an aria-label override), and any extra attribute spreads onto the root.

Step 3 — Compose a small form

<script>
  import Form from "$lib/components/Form/Form.svelte";
  import Field from "$lib/components/Field/Field.svelte";
  import Label from "$lib/components/Label/Label.svelte";
  import TextInput from "$lib/components/TextInput/TextInput.svelte";
  import Button from "$lib/components/Button/Button.svelte";

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

<Form onsubmit={handleSubmit}>
  <Field>
    <Label for="name">Full name</Label>
    <TextInput id="name" label="Full name" bind:value={name} required />
  </Field>
  <Button type="submit">Save</Button>
</Form>

TextInput's value is a $bindable rune prop, so bind:value gives you two-way binding straight away.

Check your work: run the app and the form looks plain — exactly as intended: styling is still ahead. Press Tab: focus moves through every control, the label is announced, and submit works. The behaviour is done; the look is yours, and it's next.

Step 4 — Style it

Everything above renders as pure structure, ready for your styles. Style the class hooks — .button, .text-input, .label, .field — in your global stylesheet, or link a ready-made theme in app.html:

<link rel="stylesheet" href="/themes/dark.css" />

Per-instance tweaks go through the class prop, which appends to the base hook: <Button class="wide"> renders class="button wide".

Step 5 — See it all working

The SvelteKit example app is the same catalog fully styled, with live demos, Playwright e2e tests, and an axe-core accessibility baseline:

git clone https://github.com/LilyDesignSystem/lily-design-system-svelte-sveltekit-examples
cd lily-design-system-svelte-sveltekit-examples
pnpm install && pnpm run dev

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.