Tutorials
Angular tutorial
Angular 20 standalone components — signal inputs, OnPush change detection, inline templates, zero NgModules, zero CSS.
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 Angular 20 project works — Angular CLI or Analog.
Step 1 — Get the code
git clone https://github.com/LilyDesignSystem/lily-design-system-angular-headless
cd lily-design-system-angular-headless
pnpm install
pnpm test # vitest + TestBed, all 490 components
pnpm run build-storybook Components are flat files in components/ — Button.ts, TextInput.ts — each a standalone
component with an inline template, plus its spec and story. The barrel index.ts re-exports every component; pnpm build produces an Angular Package Format bundle via ng-packagr.
Step 2 — Your first component
import { Component } from "@angular/core";
import { Button } from "lily-design-system-angular-headless";
@Component({
selector: "app-save-bar",
standalone: true,
imports: [Button],
template: `<lily-button (click)="save()">Save</lily-button>`,
})
export class SaveBar {
save() { console.log("saved"); }
} Every component declares a lily-* selector and renders the
semantic element with its class hook — <button class="button" type="button"> here. Inputs are
signal-based: label() for an aria-label override, className() to append classes.
Step 3 — Compose a small form
import { Component, signal } from "@angular/core";
import { Form, Field, Label, TextInput, Button } from "lily-design-system-angular-headless";
@Component({
selector: "app-contact-form",
standalone: true,
imports: [Form, Field, Label, TextInput, Button],
template: `
<lily-form (submit)="handleSubmit($event)">
<lily-field>
<lily-label for="name">Full name</lily-label>
<lily-text-input id="name" label="Full name" [(value)]="name" />
</lily-field>
<lily-button type="submit">Save</lily-button>
</lily-form>
`,
})
export class ContactForm {
name = signal("");
} Input-like components expose model() values, so banana-box [(value)] two-way binding just works — zoneless-compatible
throughout.
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
Style the class hooks — .button, .text-input, .label, .field — in styles.css, or
add a ready-made theme to the styles array in angular.json / link it in index.html:
<link rel="stylesheet" href="/themes/adobe-spectrum.css" /> Step 5 — See it all working
The Angular + Analog example app shows the full catalog with file-based routing and Vite:
git clone https://github.com/LilyDesignSystem/lily-design-system-angular-examples
cd lily-design-system-angular-examples
pnpm install && pnpm run dev Next steps
- Theming — runtime switching with the Angular theme-select helper.
- Preference helpers — language and text size.
- Component catalog — props, ARIA, and keyboard per component.
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.