Help
Getting started with Lily
Pick a framework, clone the headless repo or the example app, and start composing your own pages. Prefer a guided path? Start with the tutorials.
Install
Lily is published as separate Git repos per framework. The fastest way to try it is to clone the headless repo for your stack:
git clone https://github.com/LilyDesignSystem/lily-design-system-react-headless
cd lily-design-system-react-headless
pnpm install The same pattern works for the other frameworks:
lily-design-system-html-headless— no install needed; copy.htmlfileslily-design-system-svelte-headless—pnpm installlily-design-system-react-headless—pnpm installlily-design-system-vue-headless—pnpm installlily-design-system-angular-headless—pnpm installlily-design-system-nunjucks-headless—pnpm installlily-design-system-blazor-headless—dotnet build
The headless libraries are designed to be cloned or vendored — treat the source as the source of truth and copy what you need. The preference helpers additionally ship as installable packages with a build and publish pipeline (npm for the JS frameworks, NuGet for Blazor).
Use a headless component
Headless components ship semantic HTML, ARIA, and props — but no CSS. Here's a Button in each framework:
HTML
<button class="button" type="button" aria-label="Save">
Save
</button> Svelte
<script>
import Button from "lily-design-system-svelte-headless/components/Button/Button.svelte";
</script>
<Button onclick={save}>Save</Button> React
import Button from "lily-design-system-react-headless/components/Button";
<Button onClick={save}>Save</Button> Vue
<script setup>
import Button from "lily-design-system-vue-headless/components/Button.vue";
</script>
<Button @click="save">Save</Button> Angular
import { Button } from "lily-design-system-angular-headless";
@Component({
imports: [Button],
template: `<lily-button (click)="save()">Save</lily-button>`,
}) Blazor
<Button OnClick="Save">Save</Button> Nunjucks
{% from "components/button/macro.njk" import button %}
{{ button({ text: "Save", type: "button" }) }}Use a styled example
The example apps include CSS, routes, and full demo pages. The fastest way
to experiment is to start the SvelteKit, Next, Nuxt, Analog, or Eleventy
example app and view the demo at /components.
git clone https://github.com/LilyDesignSystem/lily-design-system-svelte-sveltekit-examples
cd lily-design-system-svelte-sveltekit-examples
pnpm install
pnpm run dev Then open http://localhost:5173 and browse /components.
Styling and design tokens
Each component renders with a single kebab-case class on its root element.
For example, <Button> renders <button class="button">.
Style it however you like:
.button {
background: var(--my-primary);
color: #fff;
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
}
.button:hover { background: var(--my-primary-hover); } The default Lily color palette (used in the example apps) is:
- Primary:
#2563eb - Danger:
#dc2626 - Warning:
#f59e0b - Success:
#16a34a - Page background:
#f9fafb - Card background:
#ffffff
These are suggestions, not requirements. Replace them with your own brand palette and Lily comes along happily.
Ready-made themes
Don't want to write CSS from scratch? Lily ships 45 standalone reference themes in the themes/ directory. Each is one stylesheet targeting the Lily class hooks — link it and you're styled:
<link rel="stylesheet" href="/themes/united-kingdom-government-digital-service.css" /> The set covers:
- Public sector — NHS England, NHS Scotland, and NHS Wales (patient-facing and practitioner-facing variants each), GOV.UK GDS, and the U.S. Web Design System.
- Vendor-inspired — Adobe Spectrum, Mozilla Protocol.
- General purpose — light, dark, nord, dracula, wireframe, and thirty-odd more.
Theme selectors use :where(...), so your own CSS always wins
on specificity. Pair them with the theme-select helper below
for runtime switching, or follow the theming tutorial.
Preference helpers
Each framework has a companion *-helpers catalog with three
small packages that each own one user preference end to end — selection,
DOM application, and optional localStorage persistence. Each
renders a native <select>, is SSR-safe, and ships no CSS:
- theme-select — loads theme stylesheets at runtime by
swapping a managed
<link>, and setsdata-themeon the document. - locale-select — sets
langanddir(with automatic RTL detection) so your i18n library can follow; performs no translation itself. - text-size-select — sets
data-text-sizeon the document; your CSS maps each value to font sizing.
git clone https://github.com/LilyDesignSystem/lily-design-system-svelte-helpers The Svelte catalog is the canonical reference; React, Vue, Angular, HTML, Nunjucks, and Blazor ports match it contract-for-contract. See the helpers tutorial.
Accessibility
Components target WCAG 2.2 AAA. They follow these patterns:
- Semantic HTML elements over generic
<div>s. <label for="id">linking labels to inputs.aria-labelledby/aria-describedbyfor cross-references.aria-invalid+aria-errormessagefor error states.role="alert"andaria-livefor dynamic content.aria-pressed,aria-expanded,aria-currentfor state.- Roving
tabindexfor grids.
Focus indicators are intentionally consumer-supplied — Lily never paints a default focus ring that conflicts with your design. The example apps hold a clean axe-core baseline across their routes.
Internationalization
Every label, placeholder, error message, and button text is a prop. There are no hardcoded strings. Drop in your translation framework of choice — Paraglide, i18next, vue-i18n, react-intl, .resx files, anything.
For dates, numbers, and currencies, the components accept pre-formatted
strings: you format with Intl.DateTimeFormat / Intl.NumberFormat / your preferred library and pass the result.
The locale-select helper signals the chosen locale to your
i18n library by setting lang and dir on the
document root.
Testing
Each framework subproject ships its own tests using its idiomatic stack:
- HTML: WebDriverIO running real browsers.
- Svelte: Vitest +
@testing-library/svelte. - React: Vitest +
@testing-library/react. - Vue: Vitest +
@testing-library/vue. - Angular: Vitest + TestBed (via the Analog Vite plugin).
- Nunjucks: Vitest with a render helper.
- Blazor: bUnit.
Tests use Vitest's built-in matchers only — never jest-dom matchers. This keeps the test suites portable.
The example apps add Playwright end-to-end suites, axe-core accessibility
baselines, and a responsive viewport sweep.
Troubleshooting
A component renders but looks unstyled
That's headless working as intended — no CSS ships with the component. Either write CSS against the component's kebab-case class (shown on every catalog page) or link one of the 45 ready-made themes.
My CSS doesn't seem to apply over a theme
It should — theme selectors are wrapped in :where(...),
which has zero specificity. If a rule still loses, check that your
stylesheet loads after the theme's <link> and that
the selector actually matches the component's class hook.
pnpm install fails with a peer or version error
Use a current pnpm (v10+) and Node 22+. Each repo pins its framework
versions in package.json; if your global toolchain is
older, pnpm env use --global lts is the quickest fix.
Screen reader announces the wrong name for a control
Check the component's required label prop — components
that lack visible text require one, and consumer-supplied aria-label / aria-labelledby passed through
rest-props intentionally win over the built-in wiring. Each catalog
page documents the component's ARIA contract.
The theme-select helper doesn't switch stylesheets
Confirm themesUrl points at a directory the browser can
fetch (serve the themes/ files as static assets) and that
the theme slugs in your themes prop match the file names.
The helper swaps the href of one managed <link data-lily-theme-select> — inspect it in
devtools to see the URL being requested.
Something else is broken
Open an issue with a minimal reproduction on the relevant repo at github.com/LilyDesignSystem — or see community and support.
Contributing
Lily is young and welcomes collaboration. The most useful contributions right now are:
- New components (especially patterns from established design systems).
- New themes — each is one standalone stylesheet, a well-scoped first PR.
- Better example styling — show off what's possible.
- Translations of example app strings.
- Bug reports with a minimal reproduction.
- Accessibility audits with screen readers and assistive tech.
Open issues and PRs against the relevant repo at github.com/LilyDesignSystem.
Community and support
- Questions and bug reports — open an issue on the relevant repo at github.com/LilyDesignSystem.
- Email — the maintainer reads joel@joelparkerhenderson.com and welcomes collaboration, guidance, and feedback.
- Mirrors — Lily is also pushed to Codeberg and GitLab, so you can participate from the forge you prefer.
- Conduct — the project follows a standard code of conduct; be kind, assume good faith.
FAQ
Why headless instead of styled?
Pre-styled components are convenient — until they don't match your brand. Headless components are slightly more work up front but give you total control over the visual design. The example apps and the 45 themes show ways to style them; you can take those or replace them entirely. The longer argument is on Why Lily.
Why so many components?
Lily aims to cover the patterns most apps need without forcing you to build them from scratch — including deep cuts like national identifier inputs and editorial scrollytelling. The catalog draws from a dozen established design systems plus original work — see About.
Can I use Lily with Tailwind?
Yes. Each component exposes a single kebab-case root class plus a
consumer-provided className / class. Layer
Tailwind utilities on top however you like.
Can I use Lily with semantic CSS frameworks like DaisyUI?
Yes. The kebab-case class names on the root element work as semantic CSS hooks. Pair Lily with a semantic framework and you get pre-styled components that still respect ARIA and i18n.
Is there an npm package?
The preference helpers ship as packages with an npm/NuGet publish pipeline. The headless component libraries are designed to be cloned or vendored — the source is the deliverable, so you can read, trim, and own exactly what you ship. Registry publishing for the headless libraries remains on the roadmap.
Why multi-licensed?
Different projects have different license needs. BSD and MIT are permissive, Apache-2.0 has a patent grant, and the GPL options support copyleft. Choose whichever fits your situation.
How do I report a bug or request a feature?
Open an issue on the relevant GitHub repo, or email joel@joelparkerhenderson.com.