Vue tutorial

Vue 3 single-file components wi typed props, defineModel two-wey binding, an v-bind="$attrs" pass-through.

Whit ye'll big

A wee contact form — labelled input, submit button, v-model bindin, real keyboard an screen-reader behaviour — first bare, then styled, in aboot 10 minutes.

Afore ye stairt

Ye need git, Node 22 or later, an pnpm 10 or later. Onie Vue 3 project wirks — Nuxt, Vite, or plain Vue.

Step 1 — Git the code

git clone https://github.com/LilyDesignSystem/lily-design-system
cd lily-design-system/lily-design-system-vue-headless
pnpm install
pnpm test        # vitest + @testing-library/vue
pnpm run storybook   # browse all 571 components

The components are flat files in components/ — Button.vue, TextInput.vue — ilkane wi its test, story, an docs alangside. Copy the files ye need, or vendor the repo.

Step 2 — Yer first component

<script setup>
import Button from "lily-design-system-vue-headless/components/Button.vue";
</script>

<template>
  <Button @click="save">Save</Button>
</template>

Renders <button class="button" type="button">. Props match the ither frameworks: type, disabled, pressed, label, plus attribute fall-through for aathing else.

Step 3 — Compose a wee form

<script setup>
import { ref } from "vue";
import Form from ".../components/Form.vue";
import Field from ".../components/Field.vue";
import TextInput from ".../components/TextInput.vue";
import Button from ".../components/Button.vue";

const name = ref("");
</script>

<template>
  <Form @submit="handleSubmit">
    <Field label="Full name" required>
      <TextInput id="name" label="Full name" v-model="name" required />
    </Field>
    <Button type="submit">Save</Button>
  </Form>
</template>

Field taks the visible label as its ain prop raither than a nestit label component — pass it ance, on Field, an TextInputon label supplies the accessible name.

Verified 2026-08-29: an earlier draft nestit a separate Label inside Field, which renders twa labels (ane empty) because Field aareadies renders its ain frae the label prop — catcht bi compilin the exact snippet. The contact-form composed route is the same pattern, exercised bi real e2e an axe tests.

TextInput uises defineModel, sae plain v-model wirks oot o the box.

Check yer wirk: rin the app an the form looks plain — exactly as intendit: styling is still aheid. Press Tab: focus muives through ilka control, the label is announced, an submit wirks. The behaviour is deen; the look is yours, an it's neist.

Step 4 — Style it

Style the class hooks — .button, .text-input, .label, .field — in yer stylesheet, or link a ready-made theme in index.html / nuxt.config:

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

Step 5 — See it aw wirkin

The Nuxt example app is the full catalog styled an demoed, wi Playwright e2e an an axe-core baseline:

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

Neist steps

Questions alang the wey? The help page is fu o answers — an if a step could be clearer, tell us an we'll gladly improve the tutorial.