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.
driftwood
Layout
Actions
Forms
- Field
- Input
- FileInput
- FileList
- Textarea
- CodeEditor
- Combobox
- RadioGroup
- NumberInput
- InlineEdit
- Label
- Select
- MultiSelect
- DatePicker
- TagsInput
- Checkbox
- Switch
- Slider
Feedback
Charts
Overlays
Navigation
Generated from the source by pnpm wiki · repository · v0.24.0