UI Kit
Button
Reusable <Button> with a TS-typed variant union. This page is the canonical
reference — pick the variant whose intent matches, not whose style looks close.
When to use each variant
primary— single main CTA per surface. One per view.secondary— alternate action at similar visual weight (e.g. "Export" alongside "Save").ghost— cancel / escape hatch. Use alongsideprimaryordangerso the confirm action reads first and cancel recedes.danger— destructive confirms only (delete, revoke). Never for reversible actions.default— neutral glass button; standalone actions with no clear CTA hierarchy.iconOnly— compact icon-only. Non-danger = ghost icon (light gray hover).variant="danger"= outlined red circle.
Checkbox
Native <input type="checkbox"> styled via accent-color. Add role="switch" when the semantic is on/off. Backing CSS lives in src/app.css — no Svelte
component needed.
Badge
pill (default) for muted status markers; chip for colored category tags. tone picks the color; icon adds a leading glyph; width fixes column width
for grid alignment.
IconBadge
An icon with an optional numeric count overlay — use for notification dots on nav items or anywhere a count needs
to sit on top of an icon. The dot is hidden when count is 0 or omitted. Capped visually at "99+". The badge is aria-hidden; supply accessible context via the parent element's
label.
NoticeBanner
An informational strip with a leading icon, body text, and an optional call-to-action. Use tone="muted" (default) for static warnings or configuration notices; tone="accent" for actionable prompts. Provide href + cta to render the banner as a link.
Toasts
Auto-dismiss after 4 s by default. Use success for completed mutations, error for
failures (always with a message), info for neutral notices. Toast is mounted once in the root layout
— don't remount it.
Skeleton
Inline loading placeholder shown inside <svelte:boundary> pending snippets. Appears after a 150
ms delay so sub-threshold loads never flash. Use radius="circle" for avatars, height + width to match the content it replaces.
Spinner
Inline loading indicator for in-flight mutations (button disabled + spinner). Inherits currentColor so it adapts to button variants. Prefer Skeleton for content areas; reserve Spinner for
buttons and small status indicators.
Tooltip
Uses the CSS popover="hint" API with interestfor — no JS event listeners. Only works on
interactive elements (<button>, <a>). Appears after a brief interest delay; top-end aligns the right edge of the tooltip to the right edge of the trigger.
Pagination
Self-hiding when totalPages ≤ 1. Bind page and pass totalPages; optionally
add pageSize + total for an item-range label. Page clamping is handled internally.
ProjectIcon
Renders a color swatch when no iconUrl is set; falls back to an <img> when one is
provided. size controls both dimensions. Default size is 40 px.
Modal
Slide-up dialog — header + scrollable body + optional footer. Parent owns open; onclose fires on × / ESC / backdrop.
Props
actionssnippet — footer buttons. Omit for body-inline actions.busy— disables × and setsaria-busy. Handle ESC/backdrop inoncloseto suppress them during long ops.- Bottom-sheet on mobile (<640px).
Tabs
Underlined link-tabs nav. Active tab is computed from page.url.pathname against each href; pass an array of { href, label, testId? }. The component owns its own margin-bottom — don't wrap in spacing utilities. The UI Kit's own top nav (Components / Organisms /
Tokens / Charts) uses this primitive.
Disclosure
Inline collapsible <details> with an animated chevron. Strips the global card treatment so it
reads as auxiliary copy, not a panel. Use when you want to hide reference text behind a small "When to use"
affordance — e.g. the Button variant guide above.
Show details
- Disclosure is for inline help text, not panels — it shouldn't host primary actions.
- Use the title as a read-out, not a question — "When to use each variant", not "Need help?".
- Pair with a parent that owns its own padding; the Disclosure has none.
AiButton
Round outlined sparkle button — the visual primitive for any "Improve with AI" trigger. Pure presentational; the
consumer owns the click handler and any preview/diff panel. Pass shimmer on lone surfaces (a single
textarea) to draw attention; leave it off on dense lists (a 100-row changelog) where animation would be noise.
Pass pressed to show the active "panel open" treatment.
LimitedTextarea
Textarea that visualizes how close the input is to its character limit. Bar is inset inside the textarea bottom
(green → amber → red, red zone reserved for the final ~8%) and a numeric counter sits below. max is
required; all other textarea props are forwarded. maxlength defaults to max so the
browser truncates pasted input at the cap. Screen readers get the count via aria-describedby linking
the textarea to the counter element.