Tutorials
Theming
Lily components carry stable class hooks and zero CSS — so a theme is just a stylesheet. This tutorial goes from "link one file" to "let the user switch themes at runtime, persisted".
What you'll build
An app whose entire look the user can change — starting with one linked stylesheet, ending with a persisted runtime theme switcher — in about 15 minutes.
Before you start
Finish the framework tutorial for your stack first, so you have a small Lily form to theme. Everything here works in all seven frameworks; code samples show Svelte and plain HTML.
Step 1 — Link a ready-made theme
The themes/ directory ships 45 standalone stylesheets. Copy them into your app's static assets and link one:
<link rel="stylesheet" href="/themes/united-kingdom-national-health-service-england-for-patients.css" /> Highlights of the set:
- Public sector — NHS England / Scotland / Wales, each
in patient-facing and practitioner-facing variants; GOV.UK
(
united-kingdom-government-digital-service); USWDS (united-states-web-design-system). - Vendor-inspired —
adobe-spectrum,mozilla-protocol. - General purpose —
light,dark,nord,dracula,wireframe,corporate,synthwave, and many more.
Check your work: reload the page. The same markup you built in the framework tutorial is now fully styled — buttons, inputs, labels, focus rings. One line of HTML did that — every component stayed exactly as it was.
Step 2 — Override anything
Theme selectors are wrapped in :where(...), which has zero
specificity — so any rule you write wins, plain and simple:
/* After the theme link: your brand button, everything else themed */
.button {
background: #7c3aed;
border-radius: 9999px;
} Step 3 — Runtime switching with theme-select
The theme-select helper is a native <select> that owns the whole theme lifecycle: it loads
the chosen stylesheet by swapping one managed <link> (only the active theme is ever fetched), sets data-theme="{slug}" on the document, and optionally
persists to localStorage. Svelte shown here; React, Vue,
Angular, HTML, Nunjucks, and Blazor ports match contract-for-contract:
<script>
import ThemeSelect from "lily-design-system-svelte-theme-select";
</script>
<ThemeSelect
label="Theme"
themesUrl="/themes/"
themes={["light", "dark", "nord", "wireframe"]}
storageKey="my-app-theme"
/> That's the whole integration. Keyboard behaviour is the native select's;
the control itself is headless (class hook .theme-select), so it picks up whatever theme is active.
Step 4 — React to the theme in your own CSS
Because the helper sets data-theme on the document root,
your own styles can branch on it:
[data-theme="dark"] .site-logo { filter: invert(1); } Notes for server rendering
The helper performs all DOM writes inside the framework's mount/effect
lifecycle, so it is SSR-safe by construction. To avoid a first-paint
flash, render the initial theme's <link> and data-theme on the server (for example from a cookie) — the
Svelte helper repo ships a runnable SvelteKit cookie example, and the
other catalogs document the same pattern for their stacks.
Next steps
- Preference helpers — the same pattern for language and text size.
- Contribute a theme — each is one standalone stylesheet targeting the class hooks; see contributing.
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.