19 Home
L4stIdi0t edited this page 2026-10-09 22:01:06 +02:00

driftwood

driftwood

A hand-written Svelte 5 + Tailwind v4 component kit — 58 components, zero UI dependencies. No shadcn, no Melt, no Bits: every interactive component implements its own ARIA roles, states and keyboard handling, and everything is themed by CSS custom properties, so one token block re-themes the whole app.

Currently v0.24.0. Changelog · Getting started · Theming · Forms · Overlays

pnpm add @l4st/driftwood @lucide/svelte tw-animate-css
<script lang="ts">
	import { Button, Card, toast } from '@l4st/driftwood';
</script>

Which one do I want?

I need… Use Because
A value from a short list Select Up to a few dozen options. No text entry, so nothing can be mistyped.
A value from a long list Combobox Filters as you type. Past ~30 options, arrow-scanning stops being usable.
Several values at once MultiSelect Stays open after each pick, and summarises the count on the trigger.
Several free-form values, or picks plus new ones TagsInput Enter turns typed text into a tag. Give it options and it suggests from them too.
A date DatePicker A calendar under the field, a bottom sheet on a phone. The value is a YYYY-MM-DD string.
Two to five visible options RadioGroup One tab stop; arrows move and select. No hidden state.
A yes/no that applies immediately Switch A setting that takes effect on the spot.
A yes/no in a form Checkbox Submitted with the form, and can be indeterminate.
A blocking question Dialog Modal: focus trapped, page inert, Escape closes.
A panel of work off to one side Sheet The same modal contract, anchored to an edge. Room for a form a Dialog would crowd.
A panel that comes up from the bottom Drawer Snap points, dragged or keyed between. What a phone map does over its map.
A panel of content near a control Popover Anchored, dismissible, focus moves in. Not modal.
A list of commands Menu Roving focus, activates on Enter. You open it and say where.
One field that finds anything and runs it CommandPalette The ⌘K box: grouped rows, results from the server as you type, and it runs an action instead of setting a value.
Commands on right-click ContextMenu Wraps a region; opens a Menu on right-click, Shift+F10 or a long-press.
A hint about a control Tooltip Text only, on hover and focus. Never put a control inside one.
A message that stays on the page Alert Part of the layout, and still there after a reload.
A message about what just happened Toaster Transient, bottom-right, announced. Use toast().
Progress with a known end Progress A value between 0 and 100.
Work of unknown length Spinner Or <Progress value={null} /> for a bar.
Content arriving Skeleton In the shape of what is coming, not a centred spinner.
A figure usually read, sometimes set exactly InlineEdit Plain text that becomes a field when clicked. No permanent input, no dialog.
A labelled control with an error Field Wires the label, hint, error, aria-describedby and aria-invalid.
Source code the user edits CodeEditor Highlighted, foldable, with its own undo stack. Bring your own grammar.
Files from the user FileInput Drag, drop, browse or paste. variant="line" is Input-sized; the default is a drop area.
Text the user will paste elsewhere CopyButton Copies on click, shows a check or a cross, and announces the result.
Files already chosen FileList Name, size, progress and a remove button. FileInput renders one for you.
A value over time LineChart Two or more series compared at one x, with a crosshair.
Magnitude across categories BarChart Grouped or stacked, vertical or horizontal.
Parts of a whole DonutChart Up to five parts, with the total in the middle.
Many parts of a whole, with names ShareBar One bar and a list under it; hover links the two, a row can pick its part.
A number against its range Gauge One figure, optionally banded good-to-bad.
Two axes and one number Heatmap A sequential ramp; the shape of a pattern, not exact values.
More content than fits Scroller Scrolls either way with a button at each end, hidden scrollbar, and a load-more channel.
A trend beside a number Sparkline Glyph-sized, no axes. Or trend on a StatCard.

All 58 components

Layout

Card
Card
Surface for a block of related content: an optional title and description header, then whatever you render inside
StatCard
StatCard
A single number worth looking at — the unit of a dashboard's top row
Accordion
Accordion
A set of disclosure sections
AccordionItem
AccordionItem
One section of an Accordion: a native <button aria-expanded aria-controls> disclosing a labelled region
Collapsible
Collapsible
Fully self-contained accessible disclosure — no UI dependency
Separator
Separator
A rule between things
Table
Table
A styled <table>, not a data grid
Scroller
Scroller
A strip of content that scrolls, with a button at each end

Actions

Button
Button
The kit's primary action
ButtonGroup
ButtonGroup
Visually joins a row of <Button>s into a segmented group
CopyButton
CopyButton
Copies text to the clipboard and says whether it worked: the copy icon turns away, a check springs in and draws its own stroke (a cross draws and shakes), a tint blooms across the button, and after resetAfter it all settles back
Toggle
Toggle
Accessible toggle button (aria-pressed) — for on/off controls like a formatting button or a favourite
Menu
Menu
Accessible floating menu (APG menu): roving focus, ↑/↓/Home/End, Enter/ Space activate, Escape/Tab/click-outside close, focus returns to the opener
ContextMenu
ContextMenu
Replaces the browser's context menu over a region with a Menu

Forms

Field
Field
Label, control, hint and error as one unit, with the ids wired up
Input
Input
Accessible text input
FileInput
FileInput
Picking files, in the two shapes a form actually wants: svelte <FileInput bind:files variant="line" accept=".csv" /> a control the size of an Input, for a field that sits in a column with other fields <FileInput bind:files multiple /> a drop area, for when choosing files IS the screen Both take a drop, both open the picker on click, and both are one real <input type="file"> underneath — so Enter and Space open the picker, the file dialog is the operating system's, and a name submits with the form
FileList
FileList
The rows under a file picker: an icon for the kind, the name, the size, and a remove button
Textarea
Textarea
Multi-line input
CodeEditor
CodeEditor
A code editor: a highlighted layer behind a transparent textarea
Combobox
Combobox
Accessible combobox with list autocomplete (APG "combobox with list autocomplete, none selected"): a real <input> that filters the list as you type, with DOM focus staying in the field and the active option tracked through aria-activedescendant
RadioGroup
RadioGroup
Accessible radio group (APG radio group): one tab stop for the whole group, arrows move and select, and the checked radio is the tabbable one — which is what makes a keyboard user land back where they left
NumberInput
NumberInput
Numeric field with a unit suffix and real clamping — the thing a plain <input type="number"> does not give you
InlineEdit
InlineEdit
A value shown as plain text that becomes a field when you click it — for a figure that is read far more often than it is set: a dimension in a readout, a layer's name
Label
Label
A form label
Select
Select
Custom accessible select (APG "select-only combobox"): DOM focus stays on the trigger, the highlighted option is tracked via aria-activedescendant
MultiSelect
MultiSelect
Multi-selectable variant of this library's Select (APG "select-only combobox"): DOM focus stays on the trigger, the active option is tracked with aria-activedescendant
DatePicker
DatePicker
A date field: a trigger showing the chosen date, and a calendar to pick one from
TagsInput
TagsInput
A field that collects several short values as tags: type, press Enter (or the delimiter), and what you typed becomes a removable chip
Checkbox
Checkbox
Accessible checkbox: role="checkbox" with aria-checked (true
false "mixed" for indeterminate)

Feedback

Badge
Badge
Semantic variants — all colors resolve to theme tokens (see layout.css), so this is the single place any badge color is decided
Alert
Alert
A persistent message about the surrounding content — a warning above a form, an explanation of an empty state
Progress
Progress
role="progressbar" with aria-value*
Spinner
Spinner
A small indeterminate spinner that inherits the current text colour
Skeleton
Skeleton
Loading placeholder
Toaster
Toaster
Melt/sonner-style toaster: toasts collapse into a peeking stack at the bottom-right and fan out on hover/focus
Avatar
Avatar
A person or a thing, as a round image with a fallback
Image
Image
An <img> that holds its space, shows a ThumbHash while it loads, fades in when decoded, never shows the browser's broken-image glyph, and swaps to a dark variant with the theme

Charts

LineChart
LineChart
A value over an ordered sequence — time, almost always
BarChart
BarChart
Magnitude across a handful of named things
DonutChart
DonutChart
Parts of a whole, when the whole is worth naming and the parts are few
ShareBar
ShareBar
Parts of a whole as one bar, with the parts listed under it
Heatmap
Heatmap
A matrix of magnitudes — hour against weekday, endpoint against day, anything with two labelled axes and one number in each cell
Gauge
Gauge
One number against the range it lives in — a disk that is x% full, a load average, a score
Sparkline
Sparkline
A trend at glyph size: no axes, no grid, no labels — just the shape of a series, meant to sit inside a table cell or beside a number
ChartLegend
ChartLegend
The identity key for a chart: a swatch and a name per series

Overlays

Dialog
Dialog
Accessible modal dialog that doubles as a bottom drawer on mobile
Sheet
Sheet
A modal panel anchored to an edge of the viewport, for the work that is too wide for a Dialog and too tied to the page to be a route: filters, a detail pane, a settings drawer
Drawer
Drawer
A bottom sheet that settles at more than one height — the pattern a phone map uses: a peek showing the headline, a half height for the list, and a full height for the detail, moved between by dragging the grabber or by the keyboard
Tooltip
Tooltip
Accessible tooltip, melt-inspired: the trigger snippet receives a props object to spread onto YOUR interactive element, so aria-describedby lands on the real trigger
Popover
Popover
A floating panel anchored to its trigger — the generic surface Menu and Select are specialisations of

Navigation

Tree
Tree
Accessible tree view (APG tree pattern): role=tree/treeitem/group, roving tabindex (one item tabbable), aria-expanded/selected
Tabs
Tabs
Accessible tabs (APG tabs): role=tablist/tab/tabpanel, roving tabindex — one tab in the tab order, arrows move between them — and automatic activation, which is the APG default for cheap panels
Pagination
Pagination
Page navigation
CommandPalette
CommandPalette
The ⌘K box: one field that finds anything and runs it
Kbd
Kbd
Renders a keyboard shortcut with OS-appropriate glyphs: ⌘⇧⌥ on macOS and Ctrl/Shift/Alt everywhere else

These pages are generated from the source by pnpm wiki in the repository — a fix belongs in the component, not here.