Vue tutorial

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

Wetin you go build

Small contact form — input wey get label, submit button, v-model 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 Vue 3 project dey work — Nuxt, Vite, or plain Vue.

Step 1 — Get di 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

Di components na flat files for components/ — Button.vue, TextInput.vue — each one with im test, story, and docs beside am. Copy di files wey you need, or vendor di repo.

Step 2 — Your first component

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

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

E dey render <button class="button" type="button">. Props dey match di other frameworks: type, disabled, pressed, label, plus attribute fall-through for everything else.

Step 3 — Compose small 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 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.

Verified 2026-08-29: earlier draft nest separate Label inside Field, wey render two labels (one empty) because Field already dey render im own from di label prop — dem catch am by compiling di exact snippet. Di contact-form composed route na di same pattern, wey real e2e and axe tests don exercise.

TextInput dey use defineModel, so plain v-model dey work out of di box.

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

Style di class hooks — .button, .text-input, .label, .field — inside your stylesheet, or link ready-made theme inside index.html / nuxt.config:

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

Step 5 — See everything dey work

Di Nuxt example app na di full catalog styled and demoed, with Playwright e2e and 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

Next steps

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.