Adding a component
This page is for working on the library itself. If you only want to use a component in your own project, the installation page is what you want.
1. The component
Svelte 5 runes, a Props interface extending
the matching HTML attributes, class merged
last so callers can override, and ...rest spread
onto the root element.
<script lang="ts">
import type { Snippet } from 'svelte';
import type { HTMLAttributes } from 'svelte/elements';
import { toneSoft, type Tone } from '../tones.js';
import { useLocale } from '../locale.svelte.js';
interface Props extends HTMLAttributes<HTMLDivElement> {
tone?: Tone;
children: Snippet;
}
let { tone = 'brand', class: className = '', children, ...rest }: Props = $props();
const t = useLocale();
</script>
<div class="p-4 font-sans {toneSoft[tone]} {className}" {...rest}>
{@render children()}
</div>Take colours from the tone maps, never as raw hex. Take any user-visible string from useLocale() rather than writing it inline — that is what
keeps the library translatable.
2. Export it
Put it under the right category comment — the site reads those comments to group the sidebar and the catalogue, so the placement is the categorisation.
/* --- feedback --- */
export { default as Callout } from './components/Callout.svelte';
export type { CalloutTone } from './components/Callout.svelte';3. A demo
One file, named after the subpath. It is rendered live on the component's page and shown as the code sample, so the two can never drift apart. Import from $lib/components/…, not from the package.
<script lang="ts">
import Callout from '$lib/components/Callout.svelte';
</script>
<Callout tone="accent">Something worth noticing.</Callout>4. Regenerate
bun run registry # picks up the new component
bun run exports # adds @nqmcreative/ui/callout
bun run lint # fails if either is stale
bun run build # rebuilds dist/, which is committedAfter that the component appears in the nav, the index, the ⌘K palette and the CLI without any
further wiring. dist/ is committed, so rebuild and
commit it with your change — bun cannot build the package at install time.
Add a one-line description in scripts/generate-registry.mjs; the whole catalogue is
kept there so the wording can be read together.
Two traps that fail silently
Interpolated class names
Tailwind scans source text. A class it never sees written out is never generated — no error, just an unstyled element.
<!-- Tailwind only sees literal class strings -->
<div class="bg-{tone}">…</div> <!-- generates nothing -->
<div class={toneFill[tone]}>…</div> <!-- correct -->Two colours for one property
Which wins is decided by the order the rules appear in the stylesheet, not the order in your class attribute. Set the colour once per variant, or use a side-specific utility.
<!-- two colours for the same property: CSS order decides, not class order -->
<div class="border border-hairline border-brand">…</div> <!-- unpredictable -->
<div class="border border-hairline border-l-brand">…</div> <!-- fine -->The guards will catch some of this
bun run lint fails when the exports map or the registry is stale, so
a component cannot ship without its subpath. It cannot catch a missing Tailwind class — only looking
at the result does.