3 Getting started
L4stIdi0t edited this page 2026-09-14 21:13:23 +02:00

Getting started

driftwood is published to this instance's package registry, not npmjs.com. Map the scope once, in the app's .npmrc:

@l4st:registry=https://code.letsnot.run/api/packages/L4stIdi0t/npm/

Reading needs no credentials — the repository is public. Then:

pnpm add @l4st/driftwood @lucide/svelte tw-animate-css

What those peers are for

The app provides these; the kit bundles none of them:

Peer Why If it is missing
svelte ^5.56.1 runes, $props.id() nothing works
tailwindcss ^4.3.0 every component is Tailwind classes nothing is styled
@lucide/svelte ^1.23.0 icons inside Select, MultiSelect, Menu, Dialog, Tree, Accordion, Collapsible the build cannot resolve the import
tw-animate-css ^1.4.0 animate-in / fade-in-0 / zoom-in-95 in Dialog and Tooltip the build passes and the entrance animation silently disappears — those classes simply do not exist

Install the peers explicitly — pnpm add @lucide/svelte tw-animate-css. Both package managers auto-install a missing peer, so the build succeeds either way, but vite dev then fails to render with

Unknown file extension ".svelte" for …/@lucide/svelte/dist/icons/a-arrow-down.svelte

because vite-plugin-svelte only adds Svelte packages it can see in your dependencies to ssr.noExternal; an auto-installed peer is invisible to it and Node's ESM loader is handed a .svelte file. tw-animate-css has to be in your CSS regardless.

Wire up the CSS

In your app's app.css — verified against a clean SvelteKit app that installed the packed tarball and built:

@import 'tailwindcss';
@import 'tw-animate-css';             /* required: Dialog and Tooltip entrances */

@import '@l4st/driftwood/base.css';   /* .indeterminate-bar, reduced-motion */

/* Your palette. Copy the token list from @l4st/driftwood/tokens.css and change
   the values — that is where your app gets its identity. Or import
   '@l4st/driftwood/tokens.css' for the neutral default. */
:root { --background: …; --primary: …; --radius: 0.625rem; /* … */ }

@import '@l4st/driftwood/theme.css';  /* tokens -> utilities, and the @source */
@import '@l4st/driftwood/code.css';   /* only if you use CodeEditor */

Order matters in one direction: theme.css last. Its @theme inline block and its @layer base defaults have to see the token names, and it is what maps them onto utilities. Your palette can sit anywhere before it.

theme.css carries @source "../components", which is what keeps the kit's own classes in your build — Tailwind skips node_modules otherwise. Verified twice: a Tailwind CLI run with source(none) and no consumer markup at all still emits bg-primary, border-border and .indeterminate-bar; and the clean consumer app's built stylesheet contains those plus animate-in and zoom-in-95.

Dark mode follows the system

The default palette needs no wiring: both themes live in one token block via light-dark(), and :root declares color-scheme: light dark. No class on <html>, no script, no flash-of-wrong-theme. That color-scheme declaration is the part a media query alone never gave you — it is what makes the browser's own furniture (scrollbars, form controls, autofill, the caret) match the theme.

tokens.css also ships a single @media (prefers-color-scheme: dark) rule. It only restates that declaration, but it is the query some dark-mode extensions (Noir, for one) look for. A site without it looks light-only to them, and they repaint a dark theme that is already there. Keep it if you write your own palette.

Add this to app.html so the first paint is right too:

<meta name="color-scheme" content="light dark" />

To pin a theme, flip color-scheme; every token follows on its own:

:root.dark,  :root[data-theme='dark']  { color-scheme: dark; }
:root.light, :root[data-theme='light'] { color-scheme: light; }

Both selectors ship in tokens.css, so a class-based switcher like mode-watcher works alongside the system default with nothing added. For a three-way switcher, remove the attribute for "system" rather than writing data-theme="system" — absence is what hands the decision back to the OS.

No component uses a dark: variant — they read tokens only — so an app with its own palette can keep using two blocks (.dark, or a media query) instead; nothing in the kit cares how the tokens are produced. light-dark() needs Chrome 123 / Safari 17.5 / Firefox 120.

Verified in a real browser (pnpm probe): surfaces, text and body background all change with the emulated OS theme, opacity modifiers like bg-primary/90 resolve per theme through Tailwind's color-mix(), and both pinning strategies override the system.

Full detail — how light-dark() and color-scheme combine, writing your own palette, switchers, older browsers: Theming.

Use it

<script lang="ts">
	import { Button, Card, Dialog, toast } from '@l4st/driftwood';
	import { cn } from '@l4st/driftwood/utils';
</script>

Mount <Toaster /> once, in your root +layout.svelte.

Then what

  • Home lists every component, with a table for picking between the ones that overlap.
  • Forms covers name, Field and the two-callback controls.
  • Overlays covers portalling, placement and the modal contract.
  • Theming covers the tokens, the system theme and checking your palette's contrast.
  • Changelog lists what each release moved — including the token and API diffs, which are the parts that break an app quietly.