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-inspiredadobe-spectrum, mozilla-protocol.
  • General purposelight, 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.