Theming
Nothing in a component says dark:, and nothing
hardcodes a hex value. Every utility is built on tokens, so the whole system moves together.
Eight tones
Every tonal component takes the same tone prop.
anywhere
<Button tone="danger">Delete</Button>
<Badge tone="warning" dot>Quota</Badge>
<Alert tone="success" title="Deployed" />
<Progress tone="violet" value={40} /> brand accent violet info success warning danger neutral
Four steps each
Base, hover, light (a tinted surface) and border. Every one is a Tailwind utility: bg-accent, hover:bg-accent-hover, bg-accent-light, border-accent-border.
brand
accent
violet
info
success
warning
danger
neutral
theme.css
@theme {
--color-brand: #1c6358;
--color-brand-hover: #155047;
--color-brand-light: #e8f3f1;
--color-brand-border: #a8ccc5;
/* …and the same four steps for the other seven tones */
}Making it yours
Redeclare any token after importing the theme. Nothing else has to change — every component
reading brand follows.
src/app.css
@import 'tailwindcss';
@import '@nqmcreative/ui/theme.css';
/* your brand, on top of the defaults */
@theme {
--color-brand: #0f766e;
--color-brand-hover: #115e59;
--color-brand-light: #ecfdf5;
--color-brand-border: #99f6e4;
}Light and dark
The root element decides:
| root | result |
|---|---|
| <html class="dark"> | dark, explicitly chosen |
| <html class="light"> | light, explicitly chosen |
| <html> | follows the OS preference |
ThemeToggle writes that class and remembers
the choice under nqm-theme:
anywhere
<script lang="ts">
import ThemeToggle from '@nqmcreative/ui/theme-toggle';
</script>
<ThemeToggle />
<ThemeToggle variant="segmented" />One light frame on first load
The class is applied after hydration, so someone who chose dark sees a light flash. The inline
script in
app.html from the installation page prevents it — and its
key has to match what ThemeToggle writes.In your own components
Import the tone maps rather than writing colour classes by hand.
MyThing.svelte
<script lang="ts">
import { toneSoft, toneFill, type Tone } from '@nqmcreative/ui/tones';
let { tone = 'brand' }: { tone?: Tone } = $props();
</script>
<div class={toneSoft[tone]}>…</div>Never interpolate a class name
Tailwind only sees literal strings in your source. A constructed class silently produces no CSS.
the trap
<!-- Tailwind only sees literal class strings -->
<div class="bg-{tone}">…</div> <!-- generates nothing -->
<div class={toneFill[tone]}>…</div> <!-- correct -->