Remix Kit
v0.1.0 · showcase · 59 specimens · both schemes
Production-ready interface primitives for Remix 3. Every specimen below is rendered from the shipped component, with usage and generated API details beside it.
Browse 59 specimens
This catalog is server-rendered. Native controls work here; components that need client behavior are documented as static previews. Try every island live.
Prose
The typography tier: proseCss() carries the drafting register into flowing content — rendered markdown, release notes, long-form help. In-prose links underline at rest (the RESOLVED-50 law baked in); blockquotes wear the Callout gutter, never italics — the kit has no italic register. 65ch measure.
Release notes
Body text with a link, inline code on the quiet channel, and weight where it earns it.
Hues mark; they never carry text.
<div mix={proseCss()}>{renderedMarkdown}</div>Diff block
Diffs in the drafting grammar: the CodeBlock scroll contract over add/del/ context lines, grounds on the Badge recipe (status tint + ink text — hues never carry the code), muted gutter marks, forced-colors gutter edges. parseDiff turns unified-diff text into lines. Zero JS.
middleware: [staticFiles('./public'), render(), formData()], formData(), loadDatabase(db)],<DiffBlock label="Change to router.ts" lines={parseDiff(unifiedText)} />External link and mark
ExternalLink carries the leaving-the-app affordance (↗ + sr-only note) and underlines at rest — prose links must be distinguishable without color (axe caught the hover-only default on arrival). Same-tab by default; `newTab` opts in WITH rel hygiene. markCss replaces engine-yellow search highlights with the accent tint, and the live example is right there: remix docs(external).
<ExternalLink href="https://remix.run">Remix docs</ExternalLink>
<ExternalLink href={url} newTab>Reference</ExternalLink> {/* + rel hygiene */}
<mark mix={markCss()}>{matchedText}</mark>Time
Relative timestamps in the machine register: a semantic time datetime showing "2m ago" with the absolute moment on hover. Server-rendered and static by design — frames refresh times WITH the data they describe; no ticking island pretending the page is newer than its contents. formatRelativeTime is exported and boundary-tested.
<Time value={deploy.created_at} now={now} /> {/* one `now` per page — rows agree */}Code block
Plain mono on the quiet channel, horizontally scrollable as a keyboard- reachable NAMED region (the Table scroll contract applied to code). No syntax highlighting by design — a highlighter is a dependency and a palette outside the tokens. Zero JS; position:relative for composed CopyButtons. Every usage snippet on this page is one.
<CodeBlock code={cmd} label="Install command" lang="sh" />CSS utilities
Progressive layout and typography recipes: truncation, line clamping, balanced/pretty wrapping, query containers, stable scrollbar gutters, logical safe-area padding, and native :user-invalid paint.
This longer paragraph demonstrates a two-line clamp while preserving the complete source text for layouts that remove the utility.
one-line-machine-register-value-that-is-too-long-for-the-container
<div mix={containerCss({ name: 'card' })}>…</div>
<h2 mix={textWrapCss('balance')}>…</h2>
<p mix={lineClampCss(3)}>…</p>
<div mix={safeAreaPaddingCss({ blockEnd: '16px' })}>…</div>Themes
Themes are ALTERNATIVES, not layers: inline exactly one of themeStylesheet (petrol) or graphiteStylesheet (neutral greys, brand cobalt-indigo accent) — every component runs unchanged because nothing outside the tokens file knows a hex. Both themes hold AA in both schemes (gate-walked, 430 text nodes each). The card below is graphite, scoped via css(graphiteTokens).
import { graphiteStylesheet } from '@kapable/remix-kit/tokens'
<style>{graphiteStylesheet}</style> {/* INSTEAD of themeStylesheet */}Chrome
/ Kbd chips, the section-label rule above each block, and the masthead double rule at the top of this page.
not shown here (js-wired): select popup · livepill sse — see the demo app.
Disclosure
The kit's only JS-off show/hide: native details/summary, with toggle, keyboard, and semantics browser-owned — no island, ever, and the demo gate proves it with JavaScript disabled. One-off reveals live here; coordinated sets are Accordion. This one is live — click it.
Advanced options
<Disclosure summary="Advanced options" defaultOpen?>
…revealed content…
</Disclosure>Steps
Discrete position in a known sequence (wizards, setup flows): numbered dots on connecting rules, done segments carrying accent. Zero JS — done steps link back (the server owns wizard position through the URL), the current step carries aria-current="step". Timeline is the vertical activity log; Progress is a continuous measure; this is neither.
<Steps label="Setup" current={1} steps={[
{ label: 'App', href: '/setup/app' }, // done → links back
{ label: 'Environment' }, // current (aria-current="step")
{ label: 'Review' }, // upcoming
]} />PageShell and CommandK
PageShell is the labeled main#main column (width narrow · md · wide) with the skip-link contract wired — the page chrome every app was hand-rolling. CommandK binds ⌘K/Ctrl+K to a CommandPalette as a kit island; kit islands mount at page level, never inside another island (nesting double-boots them — a law learned live and recorded).
<PageShell width="md">…page content…</PageShell>
<CommandK target="cmdk" /> {/* page level, like Toaster */}SideNav
The app-frame navigation rail — pure links, zero JS: the server owns "current" through the URL and the kit stamps aria-current="page". Current wears the change-bar rule (2px accent edge, ink text — never a filled pill); group labels and counts speak the machine register. The kit ships the nav; the frame is app-owned layout (App frame composition in the skill).
<SideNav groups={[
{ items: [{ label: 'Overview', href: '/', current: pathname === '/' }] },
{ label: 'deploys', items: [{ label: 'Production', href: '/prod', count: 12 }] },
]} />Pagination
Page navigation as pure links — works JS-off, no island — with the windowing algorithm frozen into tested code (a gap of exactly one page renders the page, never "1 … 3"). Machine register: mono tabular numerals; the current page thickens its rule in accent, echoing Tabs. compact renders the readout for tight rows.
<Pagination page={6} pageCount={20} href={(p) => `/deploys?page=${p}`} />
<Pagination compact page={6} pageCount={20} href={(p) => `/deploys?page=${p}`} />Breadcrumbs
Semantic path navigation — a labelled nav around an ordered list, last item current (explicit current wins, rendered as text with aria-current="page"). Pure links: works JS-off, no island. The separator is a quiet mono slash, not a chevron.
<Breadcrumbs items={[
{ href: '/', label: 'Deploys' },
{ href: '/apps/launchpad', label: 'launchpad' },
{ label: 'deploy #4821' },
]} />Tabs
Remix tabs primitives (keyboard, roving focus, aria — zero rebuilt) in the drafting grammar: the row rides a hairline rule, the active tab thickens it with the accent underline. Static here — switching runs on the remix client runtime; the demo's browser gate drives click/arrow/End on the live island.
<Tabs defaultActiveTab="overview">
<TabList label="Deploy sections">
<Tab name="overview">Overview</Tab>
<Tab name="log">Log</Tab>
</TabList>
<TabPanel name="overview">…</TabPanel>
<TabPanel name="log">…</TabPanel>
</Tabs>Toggle button
The toolbar toggle: aria-pressed on a native button, for independent stateful actions (pin, mute). Vocabulary triage — Switch is form on/off, SegmentedControl is exclusive choice, ToggleButton is independent pressed state. Pressed reads as a filled slot (raised ground + accent edge), never color alone. Live on the demo styleguide, gate-flipped.
<ToggleButton defaultPressed onPressedChange={(p) => …}>Pin</ToggleButton>
<ToggleButton label="Mute">🔇</ToggleButton>Command palette
The kit Dialog composed around the combobox's filtering listbox — zero new behavior code. Commands are links: committing navigates, so the server stays the router. Keyboard-driven (type a label prefix, arrow, Enter); the gate drives it end-to-end on the demo styleguide. Wire your own global ⌘K in an app island.
<Btn mix={onOpenDialog('cmdk')}>Commands <Kbd>⌘K</Kbd></Btn>
<CommandPalette id="cmdk" commands={[
{ label: 'Open deploys', href: '/', searchValue: ['home'] },
{ label: 'Open styleguide', href: '/styleguide', searchValue: ['sg'] },
]} />Popover
Generic anchored panel for filters, inspectors, and view options — remix popover primitives own positioning, Escape/outside light dismiss, and focus return; focus moves into the panel on open. Shares the picker surface paint. Live on the demo styleguide where the gate drives open/dismiss/focus. Pickers stay on Select/Menu/Combobox; titled content wants a Drawer.
<Popover label="View options">
<Checkbox label="Show archived" name="archived" />
<Switch label="Compact rows" name="compact" />
</Popover>Drawer
Dialog's side-panel sibling on the same native dialog contract — focus trap, Escape, backdrop, and focus-return all browser-owned. Pinned full-height to either edge with a scrolling body; the gate asserts the pin geometrically on the live island. For editors, detail peeks, and filter panels.
<Btn mix={onOpenDialog('details')}>Details</Btn>
<Drawer id="details" title="Deploy details" side="right" width="380px">
<DescriptionList items={…} />
</Drawer>Menu
Row actions and command groups on the remix menu primitives (keyboard, typeahead, checkbox/radio items — zero rebuilt), wearing the shared picker popup. The trigger is a kit Btn; glyph triggers ("⋯") are still named for assistive tech by label. The last wrapper non-goal, overturned on the record. Live on the demo styleguide — the gate opens it, selects, and watches the selection land as a toast.
<Menu label="Row actions" trigger="⋯" size="sm">
<MenuItem name="rename">Rename</MenuItem>
<Submenu label="Move to"> {/* NEW — one level; deeper wants a CommandPalette */}
<MenuItem name="move-prod" value="production">Production</MenuItem>
<MenuItem name="move-staging" value="staging">Staging</MenuItem>
</Submenu>
<Separator />
<MenuItem name="delete">Delete</MenuItem>
</Menu>
// listen: mix={onMenuSelect((e) => …e.item.name…)} on a wrapperToaster
Transient notices: mount one Toaster per page, fire toast(…) from any island (notices travel as DOM events). A toast is the receipt, never the record — frame reloads stay the truth. Polite live region, no focus stealing, hover pauses timers, fail tones stick until dismissed. Fully client-side, so it lives on the demo styleguide where the gate drives appear / stick / dismiss / auto-dismiss.
<Toaster /> {/* once per page */}
// from any island:
toast({ title: 'Deploy recorded', message: 'launchpad is live.', tone: 'pass' })
toast({ title: 'Deploy failed', message: 'Check the log.', tone: 'fail' }) // stickyDialog
Modal on the native dialog element — focus trap, Escape, backdrop, and focus-return are all browser-owned; the kit wires the title, the labelled close, and a scheme-correct backdrop veil. Openers live in islands (onOpenDialog(id)); form method="dialog" buttons close natively. Widths sm · md · lg. Drawer's side="bottom" is the mobile SHEET (full-width, ≤70dvh, slide-up, gate-pinned); Toaster covers all four corners. Below, the paint as a static non-modal preview — the demo gate drives open/close/Escape/focus on the live island.
<Btn mix={onOpenDialog('confirm')}>Delete</Btn>
<Dialog id="confirm" title="Delete deploy?" actions={
<form method="dialog">
<Btn type="submit">Cancel</Btn>
<Btn type="submit" variant="danger" value="confirm">Delete</Btn>
</form>
}>
Removes deploy #4821 from the log. This cannot be undone.
</Dialog>AlertDialog
Interruptive native decision dialog with explicit context, cancel-first focus, and app-owned actions.
AlertDialog requires showModal() for truthful modal isolation, initial focus, cancellation, and focus return. This server-rendered page does not imitate client behavior. Open the live playground.
<AlertDialog id="delete-deploy" title="Delete deploy?" description="This cannot be undone." actions={<DeleteDeployAction />} />Accordion
Disclosure set on hairline rules — remix accordion primitives, with the kit owning the heading-wraps-button structure. Single mode is collapsible by default; static here (toggling runs on the remix client runtime; the demo gate drives swap + collapse on the live island).
<Accordion defaultValue="tokens">
<AccordionItem value="tokens" title="What ships in /tokens?">…</AccordionItem>
<AccordionItem value="schemes" title="How do color schemes work?">…</AccordionItem>
</Accordion>Tooltip
Supplementary, non-interactive descriptions only. The actual trigger ownsaria-describedby; focus opens immediately, pointer hover waits, and Escape dismisses inside an island. Interactive help belongs in Popover.
<Tooltip content="Supplementary help" trigger={(a) =>
<button aria-describedby={a.ariaDescribedBy}>About this control</button>
} />Copy button
Copies its text to the clipboard: the glyph flips to a check and a toast fires when the page mounts a Toaster; insecure-context failures surface as a fail toast, never silently. Island doctrine — live on the demo styleguide, where the gate asserts the actual clipboard contents. This drop also ran the first consumer validation (fresh scaffold + file: install), which caught and fixed a real streaming-renderer fragment bug.
<CopyButton text="d0a2f7e" label="Copy commit" announce="Commit sha copied." />
<CopyButton text={url} glyphOnly label="Copy link" />Buttons
Native button host. Default is secondary; one primary per view, on the action the view exists for.
<Btn variant="primary" type="submit">Record deploy</Btn>IconButton
Native square button for glyph-only actions with a required accessible name and pending state.
<IconButton label="Refresh deploys">↻</IconButton>SplitButton
Joined primary action and menu trigger for one default command with closely related alternatives.
SplitButton combines a native primary action with a Menu whose alternatives require the client runtime. This server-rendered page does not imitate client behavior. Open the live playground.
<SplitButton primaryLabel="Deploy" menuLabel="Other deploy actions">{items}</SplitButton>Toolbar
Labelled action set that progressively enhances direct controls with roving arrow-key focus.
Every control stays independently tabbable in static HTML. Roving arrow-key focus is demonstrated in the live playground.
<Toolbar label="Editing tools"><Btn>Bold</Btn><Btn>Italic</Btn></Toolbar>Timeline
Chronological event feed: tone dots on a hairline rail, mono stamps riding each entry, a real ordered list. Zero JS — realtime feeds pair it with a frame and the LivePill. Dots are decorative; semantic state goes in a Badge.
- Deploy recorded14:02
launchpad → production
- Build started14:00
- Pushed d0a2f7e13:58
<Timeline label="Deploy activity" events={[
{ title: 'Deploy recorded', meta: '14:02', detail: 'launchpad → production', tone: 'pass' },
{ title: 'Build started', meta: '14:00', tone: 'pending' },
]} />Stat and DescriptionList
The dashboard/detail sweep, zero JS. Stat: lowercase mono caption, a big tabular-nums reading, quiet unit, and a Badge delta whose tone the APP judges (a falling number is good for latency, bad for revenue — no sign-sniffing). DescriptionList: a real dl — row layout for detail sheets, stack for responsive columns.
vs. yesterday
- Region
- us-east-1
- Runtime
- node 24.3
- Commit
- d0a2f7e
<Stat label="p95 latency" value="184" unit="ms" delta="−40ms" deltaTone="pass" />
<DescriptionList layout="stack" items={[{ term: 'Region', detail: 'us-east-1' }, …]} />Sparkline
The word-sized trend beside a reading — zero-JS SVG straight from server data. It shows SHAPE, not values (anything wanting axes or hover readouts is a chart, and the kit doesn't ship charts). label is required and states what the trend MEANS; tone is the app's judgment (default neutral — data is data, not action); fill opts in. One value draws just the now-dot; empty keeps the baseline.
<Sparkline values={perDay} label="Deploys per day over two weeks, rising" tone="accent" fill={true} />
<Sparkline values={errorRate} label="Error rate, falling" />Progress
Determinate progress, fully wired (role="progressbar", name, min/max/now, clamped). No indeterminate mode — unknown-duration work is a pending Callout or the LivePill, not a bar pretending to measure.
<Progress value={3} max={8} label="Migration steps" readout="3 of 8" />Spinner
CSS-only indeterminate progress indicator with optional status semantics and reduced-motion treatment.
<Spinner label="Loading deploys" size="md" />Badges
Machine-register chip, lowercase by construction. Status speaks through the dot; the label stays readable in both schemes.
<Badge tone="pass">pass</Badge>Table
Hairline rules, tabular numerals, a labelled scroll region — no boxes.
NEW: sortable column headers as pure links — the server owns sort state through the URL (JS-off), and only the active column carries aria-sort. Plain string headers still work. The mixin tier also gained textareaCss for multi-line Fields.
<Table caption="Recent deploys" head={['app', 'status', 'when']}>…</Table>DataTable
Serializable data table with server-owned sorting, optional native row selection, and a valid empty state.
<form method="post"><DataTable caption="Deployments" columns={columns} rows={rows} selectionName="deploy" /><Btn type="submit">Apply selection</Btn></form>Identity
The "who" row — Avatar + ink name + quiet mono meta; the unit for assignee cells, comment headers, member lists. href makes the whole row one quiet link (hover surface, underlined name — not blue text). The avatar is aria-hidden here: the visible name announces once.
<Identity name="Ada Lovelace" meta="ada@example.dev" />
<Identity name={user.name} meta={<Time value={seenAt} now={now} />} href={routes.member.href({ id })} />Avatar, Separator, and Skeleton
Small pieces, zero JS. Avatar draws a deterministic identity hue from the six icon tokens (same name, same hue, both schemes) with image + initials modes and three sizes. Separator is a real hairline hr (or a vertical role="separator"). Skeleton pulses opacity only and is decorative by construction — mark the loading container, not the bones.
<Avatar name="Ada Lovelace" /> // initials AL on Ada's hue
<Avatar name="Ada Lovelace" src="/ada.png" size="lg" />
<Separator /> // <hr>; vertical via orientation
<Skeleton width="60%" /> // line · block · circleUploadList
Presentational native list for file names, sizes, transfer status, progress, errors, and app-owned actions.
- release-notes.pdf1.5 KBuploading42%
- diagram.svg512 Bfailed
The connection was interrupted.
<UploadList label="Current uploads" items={uploads} />Empty states
The one human-register moment: display serif, dashed border. The quiet variant is for screens whose serif budget the masthead already spent.
Nothing deployed yet
The serif variant, for screens without a masthead.
No results
The quiet variant, below a serif masthead.
<EmptyState title="Nothing deployed yet" hint="Record one above." />Callouts
Inline feedback as a diff-gutter change-bar — a 2px rule in the status hue plus the status dot, no fill and no box. Hues mark; they never paint surfaces or carry text. live maps to role="status"/role="alert" for callouts inserted after user action.
Deploy recorded
Deploy failed
<Callout tone="fail" title="Deploy failed" live="assertive">
The build exited non-zero. Check the log, then retry.
</Callout>Context menu and slider
ContextMenu opens the Menu machinery at the pointer (right-click or the keyboard menu key) on a labelled group — with the shortcuts law: every context action also exists as a visible control. sliderCss completes the native form-control sweep: a token-styled range input, JS-off, Field- composable; the thumb position is the readout.
<ContextMenu label="Deploy row actions" menu={<>
<MenuItem name="retry">Retry</MenuItem>
<MenuItem name="view-logs">View logs</MenuItem>
</>}>
…the row/card…
</ContextMenu>
<Field label="Failure threshold" name="threshold">
{(a) => <input type="range" id={a.id} name={a.name} mix={sliderCss()} />}
</Field>ErrorSummary
The form-level error block for SERVER-side validation (the GOV.UK pattern): render above the form after a failed POST — role="alert" announces, each error links to its field (href: '#email' + Field id="email"). Renders nothing when empty. Links are ink with underline: hues mark (the fail rule and dot), never carry text.
There is a problem
Enter your email address
12+ characters — reveal is island behavior
<ErrorSummary errors={[{ message: 'Enter your email address', href: '#email' }]} />
<Field label="Email" name="email" id="email" error={errors.email} />Dropzone
File upload as progressive enhancement over the NATIVE input: JS-off it is a working picker inside your form POST; inside an island you get drag-drop, the chosen-files list (name + formatBytes), and per-file remove. Dropped files land IN the input via DataTransfer — the form submits identically either way. The upload stays app-owned.
<Dropzone label="Attachments" name="files" multiple={true}
hint="png or pdf, up to 5 MB" /> {/* inside a clientEntry island */}TagInput
Tags over REPEATED FORM FIELDS: every chip is a hidden input named name, the entry input carries name too, and the server flattens every path with parseTags(formData.getAll(name)). JS-off: chips render static and the entry still adds (comma-separated); removal is island behavior — enter/comma commits, backspace pops, × removes.
<TagInput label="Labels" name="labels" defaultValue={deploy.labels} /> {/* island */}
// server: const labels = parseTags(formData.getAll('labels'))Combobox
Input-first picker on the remix combobox primitives (filter, typeahead, aria — zero rebuilt), wearing the same popup spec as Select via a shared internal module, so the two pickers cannot drift. Type to filter — searchValue takes alias arrays — and commit one stable hidden-input value. Filtering runs on the client runtime; the demo gate types, filters by alias, and commits live.
<Combobox name="app" placeholder="Search apps">
<ComboboxOption label="console" value="console" searchValue={['con', 'admin']} />
<ComboboxOption label="launchpad" value="launchpad" searchValue={['lp']} />
</Combobox>ComboboxMulti
Stable-value multi-picking over a real native select multiplefallback. In an island it enhances to removable chips plus the existing Combobox; both paths submit repeated values through getAll(name).
<ComboboxMulti label="Reviewers" name="reviewers" options={people} />
// server: formData.getAll('reviewers')Radio group
Native radios in a real fieldset/legend — the grouping semantics forms need. Works JS-off; group hint/error wire like Field; per-option hints and disabled; stack or row orientation. These are live — pick one.
<RadioGroup label="Environment" name="env" defaultValue="staging"
options={[
{ value: 'production', label: 'Production' },
{ value: 'staging', label: 'Staging', hint: 'Deploys still notify the room.' },
]} />CheckboxGroup
A real fieldset/legend for repeated checkbox values, with shared hint and server error wiring.
<CheckboxGroup label="Notify through" name="channels" options={channels} />Segmented control
The compact view/mode switch on native radios — arrow-key roving and form participation are browser-owned, so it works JS-off with no island. These are live — click or arrow between segments. Intent triage: RadioGroup for labelled form choices, Tabs for panels swapping in place, this for inline chrome.
<SegmentedControl label="View" name="view" defaultValue="board"
options={[
{ value: 'table', label: 'Table' },
{ value: 'board', label: 'Board' },
]} />ToggleGroup
Native radio or checkbox fieldset dressed as a joined single- or multiple-selection group.
<ToggleGroup label="Cadence" name="cadence" type="single" options={options} />Checkbox and Switch
Native checkbox hosts in a wrapping label — they submit in plain forms with JS off. Checkbox for facts that take effect on submit; Switch (announces role="switch") for settings that read as on/off. These are live — click them.
<Checkbox label="Notify the room" name="notify" defaultChecked />
<Switch label="Maintenance mode" name="maint" hint="Serves the static page; deploys queue." />DateField
Native date and local date-time controls with Field label, constraint, hint, and error wiring.
Uses local time.
<DatePicker label="Start date" name="start" min="2026-01-01" />
<DateField label="Run at" name="runAt" type="datetime-local" />InputOTP
Single native text input for one-time codes so autofill, paste, and leading zeroes remain intact.
Enter the six-digit code.
<InputOTP label="Verification code" name="code" length={6} pattern="[0-9]{6}" />Specialized form controls
Native search, textarea, number, and range controls with Field wiring, plus consistent responsive action layout.
Keep the note under 280 characters
<SearchField label="Search" name="q" />
<TextareaField label="Note" name="note" autoSize />
<NumberField label="Retries" name="retries" min={0} max={8} />
<RangeField label="Threshold" name="threshold" min={0} max={100} />
<FormActions><Btn>Cancel</Btn><Btn variant="primary">Save</Btn></FormActions>Form fields
Label, control, hint, and error fully wired for assistive tech. The error state below is a specimen, not a mistake. NEW: FormSection groups several Fields under a real legend (announced with each control) — one decision is a RadioGroup; many related Fields are a FormSection.
1–60 characters.
App name must be 1–60 characters
Native select — works JS-off.
<Field label="App" name="app" required hint="1–60 characters." />API reference
Every exported *Props interface, generated FROM SOURCE at render time — names, types, optionality, and doc lines are read out ofsrc/ui by a syntactic parse, so this reference cannot drift from the code. Required props have no ?.
Accordion · 6 props
| prop | type | notes |
|---|---|---|
| type? | 'single' | 'multiple' | 'single' (default): one item open at a time. 'multiple': independent items. |
| defaultValue? | string | string[] | Item value(s) open on first render — a string for single, string[] for multiple. |
| collapsible? | boolean | Single mode: allow closing the open item (default true — a set you cannot fully close is a trap). |
| disabled? | boolean | Disables every item. |
| children? | RemixNode | AccordionItem children. |
| mix? | MixInput |
AccordionItem · 6 props
| prop | type | notes |
|---|---|---|
| value | string | Identity within the set (matches defaultValue). |
| title | RemixNode | Trigger row content. |
| disabled? | boolean | |
| heading? | 'h2' | 'h3' | 'h4' | Heading element wrapping the trigger (default 'h3'). Match your outline — items directly under the page h1 should be 'h2'. |
| children? | RemixNode | Panel content. |
| mix? | MixInput |
AlertDialog · 8 props
| prop | type | notes |
|---|---|---|
| id | string | Document-unique id — openers target it via `onOpenDialog(id)`. |
| title | string | Dialog title; renders as the labelling heading. |
| description | string | Explicit decision context, wired to `aria-describedby`. |
| children? | RemixNode | Additional detail. App forms belong here, not around the dialog. |
| actions? | RemixNode | App-owned action controls or forms, rendered after Cancel. |
| cancelLabel? | string | Cancel button text (default 'Cancel'). |
| width? | 'sm' | 'md' | Dialog width: 'sm' 360px · 'md' 480px (default). |
| mix? | MixInput |
Avatar · 4 props
| prop | type | notes |
|---|---|---|
| name | string | Person/entity name — the accessible label, and the initials source. |
| src? | string | Image URL. Omit to render initials (no JS-off error fallback — pass src only when it exists). |
| size? | 'sm' | 'md' | 'lg' | 'sm' 24px · 'md' 32px (default) · 'lg' 40px. |
| mix? | MixInput |
AvatarGroup · 5 props
| prop | type | notes |
|---|---|---|
| people | AvatarGroupPerson[] | |
| max? | number | Discs shown before the `+N` overflow (default 4). |
| label? | string | Group accessible name (default "N people"). |
| size? | 'sm' | 'md' | 'sm' 24px (default — this is a cell component) · 'md' 32px. |
| mix? | MixInput |
Badge · 3 props
| prop | type | notes |
|---|---|---|
| children? | RemixNode | |
| tone? | BadgeTone | |
| mix? | MixInput |
Breadcrumbs · 4 props
| prop | type | notes |
|---|---|---|
| items | BreadcrumbItem[] | |
| label? | string | Accessible name for the nav. Default 'Breadcrumb'. |
| separator? | RemixNode | Separator between crumbs (aria-hidden). Default: a mono slash. |
| mix? | MixInput |
Btn · 18 props
| prop | type | notes |
|---|---|---|
| children? | RemixNode | |
| variant? | BtnVariant | |
| size? | BtnSize | |
| type? | 'button' | 'submit' | 'reset' | |
| disabled? | boolean | |
| name? | string | |
| value? | string | |
| form? | string | |
| formAction? | string | |
| formEncType? | string | |
| formMethod? | string | |
| formNoValidate? | boolean | |
| formTarget? | string | |
| title? | string | |
| ariaLabel? | string | |
| pending? | boolean | Externally controlled submission state. Disables the button and sets aria-busy. |
| pendingLabel? | RemixNode | Visible replacement while pending; defaults to the original children. |
| mix? | MixInput |
Callout · 5 props
| prop | type | notes |
|---|---|---|
| children? | RemixNode | |
| title? | string | Bold lead line; omit for a single-sentence callout. |
| tone? | CalloutTone | |
| live? | 'polite' | 'assertive' | For callouts INSERTED after user action (frame reload, mutation result): 'polite' renders `role="status"`, 'assertive' renders `role="alert"`. Leave unset for callouts that load with the page — announcing static content on every visit is noise. |
| mix? | MixInput |
Card · 4 props
| prop | type | notes |
|---|---|---|
| children? | RemixNode | |
| pad? | CardPad | |
| raised? | boolean | |
| mix? | MixInput |
CheckboxGroup · 9 props
| prop | type | notes |
|---|---|---|
| label | string | |
| name | string | |
| options | CheckboxOption[] | |
| defaultValue? | string[] | |
| disabled? | boolean | |
| hint? | RemixNode | |
| error? | string | null | |
| orientation? | 'stack' | 'row' | |
| mix? | MixInput |
CodeBlock · 6 props
| prop | type | notes |
|---|---|---|
| code | string | The code, verbatim. |
| tokens? | CodeToken[] | Optional server-rendered tokens. Falls back to `code` if their text differs. |
| lang? | string | Language hint exposed as a data attribute. |
| label? | string | Accessible name for the scroll region. Default 'Code'. |
| dense? | boolean | Tighter padding + smaller type for dense listings. |
| mix? | MixInput |
ComboboxMulti · 12 props
| prop | type | notes |
|---|---|---|
| label | string | |
| name | string | |
| options | ComboboxMultiOption[] | |
| defaultValue? | string[] | |
| inputId? | string | |
| placeholder? | string | |
| hint? | string | |
| disabled? | boolean | |
| selectedLabel? | string | |
| allSelectedPlaceholder? | string | |
| removeLabel? | (option: ComboboxMultiOption) => string | |
| mix? | MixInput |
CommandK · 1 prop
| prop | type | notes |
|---|---|---|
| target | string | The Dialog/CommandPalette id to toggle. |
CommandPalette · 5 props
| prop | type | notes |
|---|---|---|
| id | string | Document-unique id — open with `onOpenDialog(id)`. |
| commands | Command[] | |
| title? | string | Dialog title (default "Commands") — the palette's accessible name. |
| placeholder? | string | |
| mix? | MixInput |
ContextMenu · 4 props
| prop | type | notes |
|---|---|---|
| label | string | Accessible name for the menu ("Deploy row actions"). |
| menu | RemixNode | The MenuItems (plus Separators/Submenus) of the context menu. |
| children? | RemixNode | The region the right-click (or keyboard menu key) targets. |
| mix? | MixInput |
CopyButton · 7 props
| prop | type | notes |
|---|---|---|
| text | string | The text that lands on the clipboard. |
| label? | string | Accessible/visible label (default "Copy"). |
| announce? | string | Toast message on success (default "Copied."). |
| glyphOnly? | boolean | Show only the glyph (label stays for AT). |
| variant? | BtnVariant | |
| size? | BtnSize | |
| mix? | MixInput |
DataTable · 11 props
| prop | type | notes |
|---|---|---|
| caption | string | |
| columns | DataTableColumn[] | |
| rows | DataTableRow[] | |
| selectionName? | string | Adds one native row-selection checkbox per row under this form field name. |
| selectionHeading? | RemixNode | Localized heading for the optional selection column. |
| emptyTitle? | string | |
| emptyHint? | string | |
| emptyHeading? | 'h2' | 'h3' | 'h4' | |
| dense? | boolean | |
| stickyHead? | boolean | |
| mix? | MixInput |
DateField · 15 props
| prop | type | notes |
|---|---|---|
| label | string | |
| name | string | |
| id? | string | |
| type | 'date' | 'datetime-local' | |
| defaultValue? | string | |
| min? | string | |
| max? | string | |
| step? | number | 'any' | |
| error? | string | null | |
| hint? | RemixNode | |
| required? | boolean | |
| disabled? | boolean | |
| readOnly? | boolean | |
| autoComplete? | string | |
| mix? | MixInput |
DescriptionList · 3 props
| prop | type | notes |
|---|---|---|
| items | DescriptionItem[] | |
| layout? | 'row' | 'stack' | 'row' (default): terms in a left column, details beside — the detail sheet. 'stack': term above detail, wrapping into columns. |
| mix? | MixInput |
Dialog · 7 props
| prop | type | notes |
|---|---|---|
| id | string | Document-unique id — openers target it via `onOpenDialog(id)`. |
| title | string | Dialog title; renders as the labelling heading. |
| children? | RemixNode | Body content. Put app forms here, not in `actions`. |
| actions? | RemixNode | Footer row. `<form method="dialog">` buttons here close natively; confirm buttons that mutate belong to app forms in the body. |
| width? | 'sm' | 'md' | 'lg' | Dialog width: 'sm' 360px · 'md' 480px (default) · 'lg' 640px. |
| closeLabel? | string | Accessible name for the × control (i18n; default 'Close'). |
| mix? | MixInput |
DiffBlock · 4 props
| prop | type | notes |
|---|---|---|
| lines | DiffLine[] | |
| label? | string | Accessible name for the scroll region ("Change to router.ts"). |
| dense? | boolean | |
| mix? | MixInput |
Disclosure · 4 props
| prop | type | notes |
|---|---|---|
| summary | RemixNode | The always-visible summary line. |
| defaultOpen? | boolean | Open on first render (`open` attribute — still toggleable). |
| children? | RemixNode | The revealed content. |
| mix? | MixInput |
Drawer · 8 props
| prop | type | notes |
|---|---|---|
| id | string | Document-unique id — openers target it via `onOpenDialog(id)`. |
| title | string | |
| children? | RemixNode | |
| actions? | RemixNode | Footer row; `<form method="dialog">` buttons close natively. |
| side? | 'right' | 'left' | 'bottom' | Edge the panel pins to (default 'right'). 'bottom' is the sheet. |
| width? | string | Panel width for left/right (default '380px'; clamped). Ignored for bottom. |
| closeLabel? | string | Accessible name for the × control (i18n; default 'Close'). |
| mix? | MixInput |
Dropzone · 8 props
| prop | type | notes |
|---|---|---|
| label | string | Visible label ("Attachments"). |
| name | string | Form field name. |
| accept? | string | `accept` passthrough ("image/png,.pdf"). |
| multiple? | boolean | |
| hint? | string | Machine-register constraint line ("png or pdf, up to 5 MB"). |
| listLabel? | string | i18n: name for the chosen-files list (default 'Chosen files'). |
| removeLabel? | (name: string) => string | i18n: per-file remove label (default `Remove ${name}`). |
| mix? | MixInput |
EmptyState · 7 props
| prop | type | notes |
|---|---|---|
| title | string | |
| hint? | string | |
| icon? | RemixNode | |
| action? | RemixNode | |
| quiet? | boolean | Sans-serif title. Use when the screen already spent its display-serif budget (e.g. a serif masthead) — one serif element per screen, and the masthead keeps it (BRAND). |
| heading? | 'h2' | 'h3' | 'h4' | Heading element for the title (default 'h3'). Match your outline — an EmptyState directly under the page h1 should be 'h2' so levels don't skip. |
| mix? | MixInput |
ErrorSummary · 3 props
| prop | type | notes |
|---|---|---|
| errors | FormError[] | Renders nothing when empty — safe to render unconditionally. |
| title? | string | Heading (default "There is a problem"). |
| mix? | MixInput |
ExternalLink · 4 props
| prop | type | notes |
|---|---|---|
| href | string | |
| children? | RemixNode | |
| newTab? | boolean | Opt-in target="_blank" (adds rel="noopener noreferrer"). |
| mix? | MixInput |
Field · 16 props
| prop | type | notes |
|---|---|---|
| label | string | Visible label text. |
| name | string | Form field name (also forwarded to custom controls). |
| id? | string | Explicit control id — set it when an ErrorSummary link needs a stable anchor ("#email"); otherwise the render owns an internal id. |
| error? | string | null | Error message; renders in `status.fail` and sets `aria-invalid` when a non-empty string. |
| hint? | RemixNode | Assistive hint; renders in `text-muted` and joins `aria-describedby`. Takes any node — the Search composition puts a `<Kbd>` in here. |
| prefix? | RemixNode | Adornment INSIDE the field before the input ("https://", a glyph) — mono, muted, non-interactive; default-input path only. Decorative: bake meaning into `label`/`hint`, not the adornment. |
| suffix? | RemixNode | Adornment inside the field after the input ("ms", "@example.dev"). |
| required? | boolean | Marks the control required (native `required` + a quiet visual marker). |
| disabled? | boolean | Disables the default control. |
| inputType? | string | Default control's input type. `'text'` by default. |
| placeholder? | string | Default control placeholder. |
| defaultValue? | string | Default control initial value (uncontrolled). |
| autoComplete? | string | Default control `autocomplete` token. |
| children? | (attrs: FieldControlAttrs) => RemixNode | Custom control render prop. Receives the wiring attrs and OWNS the control; when provided, Field renders no default input. |
| control? | (attrs: FieldControlAttrs) => RemixNode | Same contract as function-children, as a PROP — use this inside islands: a function passed as a JSX CHILD crashes the client reconciler ("Unexpected RemixNode"; Sharp Edge 13), while a function prop is plain data. Wins over children when both are set. |
| mix? | MixInput | Composition passthrough for the wrapping element. |
FormActions · 4 props
| prop | type | notes |
|---|---|---|
| children? | RemixNode | |
| align? | 'start' | 'end' | 'between' | Horizontal distribution. Default `end`. |
| responsive? | boolean | Stack actions at the mobile page breakpoint. Default true. |
| mix? | MixInput |
FormSection · 4 props
| prop | type | notes |
|---|---|---|
| legend | string | The group name — a real `<legend>`, announced with each control. |
| hint? | string | Quiet context line under the legend. |
| children? | RemixNode | |
| mix? | MixInput |
IconButton · 18 props
| prop | type | notes |
|---|---|---|
| label | string | Accessible name for the icon-only control. |
| children | RemixNode | Icon glyph; always hidden from the accessibility tree. |
| variant? | 'quiet' | 'secondary' | 'danger' | |
| size? | 'sm' | 'md' | 'lg' | |
| type? | 'button' | 'submit' | 'reset' | |
| disabled? | boolean | |
| name? | string | |
| value? | string | |
| form? | string | |
| formAction? | string | |
| formEncType? | string | |
| formMethod? | string | |
| formNoValidate? | boolean | |
| formTarget? | string | |
| pending? | boolean | Externally controlled submission state. |
| pendingLabel? | string | Accessible name while pending; defaults to `label`. |
| title? | string | |
| mix? | MixInput |
Identity · 6 props
| prop | type | notes |
|---|---|---|
| name | string | |
| meta? | RemixNode | Quiet mono sub-line under the name. Omit for the single-line form. |
| src? | string | Avatar image URL — omit for the initials disc. |
| size? | 'sm' | 'md' | 'sm' 24px avatar (dense cells) · 'md' 32px (default). |
| href? | string | Makes the whole row a single quiet link. |
| mix? | MixInput |
InputOTP · 15 props
| prop | type | notes |
|---|---|---|
| label | string | |
| name | string | |
| length | number | |
| id? | string | |
| defaultValue? | string | |
| inputMode? | 'numeric' | 'text' | |
| pattern? | string | |
| error? | string | null | |
| hint? | RemixNode | |
| required? | boolean | |
| disabled? | boolean | |
| readOnly? | boolean | |
| autoComplete? | 'one-time-code' | 'off' | |
| enterKeyHint? | 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send' | |
| mix? | MixInput |
Kbd · 2 props
| prop | type | notes |
|---|---|---|
| children? | RemixNode | |
| mix? | MixInput |
LivePill · 3 props
| prop | type | notes |
|---|---|---|
| eventsHref | string | Href of the SSE endpoint (a route serving `sseResponse(...)`). |
| frame? | string | string[] | Named frame(s) to reload when the server version changes. Omit for an indicator-only pill (rare — most pages want the reload too). |
| ssrVersion? | number | Feed version at server render (`live.version()`) — closes the SSR→stream-open missed-mutation window. |
Masthead · 5 props
| prop | type | notes |
|---|---|---|
| title | string | The screen's h1. Display serif by default — the arrival moment. |
| readout? | RemixNode | Mono readout line under the title — REAL facts (version, sha, counts). |
| wordmark? | string | Lowercase mono wordmark above the title ("kapable"). |
| quiet? | boolean | Sans title, for screens spending their serif budget elsewhere (e.g. a styleguide whose EmptyState specimens carry it). One serif per screen. |
| mix? | MixInput |
Menu · 7 props
| prop | type | notes |
|---|---|---|
| label | string | Accessible name for the menu ("Row actions", "View"). |
| trigger? | RemixNode | Trigger content. Defaults to the label text; pass a glyph ("⋯") for icon triggers — the label still names the control for AT. |
| variant? | BtnVariant | Trigger button look (default quiet — menus are secondary chrome). |
| size? | BtnSize | |
| children? | RemixNode | MenuItem children (plus kit Separators between groups). |
| disabled? | boolean | |
| mix? | MixInput |
MenuItem · 8 props
| prop | type | notes |
|---|---|---|
| name | string | Selection identity — `onMenuSelect` events carry it. |
| type? | 'checkbox' | 'radio' | 'checkbox' | 'radio' for stateful items; omit for plain actions. |
| checked? | boolean | |
| value? | string | |
| disabled? | boolean | |
| searchValue? | string | string[] | Typeahead value(s) when children aren't plain text. |
| children? | RemixNode | |
| mix? | MixInput |
NumberField · 16 props
| prop | type | notes |
|---|---|---|
| label | string | |
| name | string | |
| id? | string | |
| error? | string | null | |
| hint? | RemixNode | |
| required? | boolean | |
| disabled? | boolean | |
| placeholder? | string | |
| defaultValue? | number | string | |
| min? | number | |
| max? | number | |
| step? | number | 'any' | |
| autoComplete? | string | |
| readOnly? | boolean | |
| unit? | RemixNode | Unit shown after the value; decorative, so include it in label/hint too. |
| mix? | MixInput |
PageShell · 4 props
| prop | type | notes |
|---|---|---|
| children? | RemixNode | |
| width? | 'narrow' | 'md' | 'wide' | Column width: 'narrow' 720px · 'md' 880px (default) · 'wide' 1100px. |
| skipLink? | boolean | Render the skip link here (only when the Document doesn't own one). |
| mix? | MixInput |
Pagination · 9 props
| prop | type | notes |
|---|---|---|
| page | number | Current page, 1-based (clamped). |
| pageCount | number | |
| href | (page: number) => string | Builds the href for a page number, e.g. (p) => `/deploys?page=${p}`. |
| label? | string | Accessible name for the nav. Default 'Pagination'. |
| window? | number | Numbers kept around the current page (default 1). |
| compact? | boolean | ‹ page N of M › — no number strip. |
| prevLabel? | string | i18n: default 'Previous page' / 'Next page'. |
| nextLabel? | string | |
| mix? | MixInput |
PasswordField · 10 props
| prop | type | notes |
|---|---|---|
| label | string | |
| name | string | |
| id? | string | Explicit control id (ErrorSummary anchor hook), like Field's. |
| error? | string | null | |
| hint? | string | |
| required? | boolean | |
| placeholder? | string | |
| autoComplete? | string | Default 'current-password'; use 'new-password' on signup forms. |
| labels? | { show: string; hide: string } | i18n: toggle strings. When set, each value is used as BOTH the visible text and the accessible name (the default English split — "show" text / "Show password" label — is a register choice, not a requirement). |
| mix? | MixInput |
Popover · 8 props
| prop | type | notes |
|---|---|---|
| label | string | Accessible name; the trigger text unless `trigger` is given. |
| trigger? | RemixNode | Custom trigger content (glyphs get named by `label`). |
| variant? | BtnVariant | Trigger button look (quiet default — popovers are secondary chrome). |
| size? | BtnSize | |
| placement? | 'bottom-start' | 'bottom-end' | Panel corner relative to the trigger (default 'bottom-start'). |
| children? | RemixNode | Panel content. |
| disabled? | boolean | |
| mix? | MixInput |
Progress · 7 props
| prop | type | notes |
|---|---|---|
| value | number | Current value, clamped to [0, max]. |
| max? | number | Upper bound (default 100). |
| label | string | Accessible name — WHAT is progressing ("Upload", "Build launchpad"). |
| readout? | boolean | string | Right-aligned mono readout: `true` renders the percentage, a string renders verbatim ("3 of 8"). Off by default. |
| size? | 'sm' | 'md' | Channel height: 'md' (6px, default) or 'sm' (4px, for dense rows). |
| variant? | 'bar' | 'ring' | 'bar' (default) or 'ring' — the compact dashboard form (28px, beside a Stat). The readout sits right of the ring, same as the bar. |
| mix? | MixInput |
RadioGroup · 10 props
| prop | type | notes |
|---|---|---|
| label | string | Group label — rendered as the real `<legend>`. |
| name | string | Shared form field name. |
| options | RadioOption[] | |
| defaultValue? | string | Value checked on first render (uncontrolled). |
| required? | boolean | Marks the group required (native `required` on its radios). |
| disabled? | boolean | Disables every option. |
| hint? | string | Group-level assistive hint; joins `aria-describedby`. |
| error? | string | null | Group-level error; `status.fail`, `role="alert"`, sets `aria-invalid`. |
| orientation? | 'stack' | 'row' | 'stack' (default) or 'row' — row wraps on narrow screens. |
| mix? | MixInput | Composition passthrough for the fieldset. |
RangeField · 11 props
| prop | type | notes |
|---|---|---|
| label | string | |
| name | string | |
| id? | string | |
| error? | string | null | |
| hint? | RemixNode | |
| disabled? | boolean | |
| min? | number | |
| max? | number | |
| step? | number | 'any' | |
| defaultValue? | number | |
| mix? | MixInput |
SearchField · 12 props
| prop | type | notes |
|---|---|---|
| label | string | |
| name | string | |
| id? | string | |
| error? | string | null | |
| hint? | RemixNode | |
| required? | boolean | |
| disabled? | boolean | |
| placeholder? | string | |
| defaultValue? | string | |
| autoComplete? | string | |
| enterKeyHint? | 'search' | 'enter' | 'go' | 'next' | 'previous' | 'send' | 'done' | |
| mix? | MixInput |
SectionLabel · 5 props
| prop | type | notes |
|---|---|---|
| label | string | |
| count? | number | string | |
| meta? | RemixNode | |
| heading? | 'h2' | 'h3' | 'h4' | Render the label as a visible section heading while keeping the rule/meta grammar. |
| mix? | MixInput |
SegmentedControl · 7 props
| prop | type | notes |
|---|---|---|
| label | string | Accessible group name (sr-only legend — segments are self-evident chrome). |
| name | string | Shared form field name. |
| options | SegmentedOption[] | |
| defaultValue? | string | Value checked on first render (uncontrolled). Defaults to the first option. |
| disabled? | boolean | |
| size? | 'sm' | 'md' | 'md' (default) or 'sm' for dense rows. |
| mix? | MixInput |
Separator · 2 props
| prop | type | notes |
|---|---|---|
| orientation? | 'horizontal' | 'vertical' | 'horizontal' (default, a real <hr>) or 'vertical' (a separator span). |
| mix? | MixInput |
SideNav · 4 props
| prop | type | notes |
|---|---|---|
| groups | SideNavGroup[] | |
| label? | string | Accessible name for the nav (default 'Sections'). |
| width? | string | Rail width (default '220px'). |
| mix? | MixInput |
Skeleton · 4 props
| prop | type | notes |
|---|---|---|
| shape? | 'line' | 'block' | 'circle' | 'line' (text-height bar, default) · 'block' · 'circle'. |
| width? | string | CSS width (default: line 100%, block 100%, circle 32px). |
| height? | string | CSS height (default: line 12px, block 64px, circle = width). |
| mix? | MixInput |
Sparkline · 7 props
| prop | type | notes |
|---|---|---|
| values | number[] | The series, oldest first. One value draws just the "now" dot. |
| label | string | Accessible summary — say what the trend MEANS, not the numbers ("Deploys per day over two weeks, rising"). |
| tone? | SparklineTone | Line hue; the app judges tone (default 'neutral'). |
| fill? | boolean | Translucent area fill under the line (default false — lines, not fills). |
| width? | number | Rendered size in px (defaults 96×28 — a word, not a figure). |
| height? | number | |
| mix? | MixInput |
Spinner · 3 props
| prop | type | notes |
|---|---|---|
| label? | string | Adds status semantics and supplies the accessible announcement. |
| size? | 'sm' | 'md' | 'lg' | |
| mix? | MixInput |
SplitButton · 18 props
| prop | type | notes |
|---|---|---|
| primaryLabel | string | |
| menuLabel | string | |
| children | RemixNode | |
| type? | 'button' | 'submit' | 'reset' | |
| name? | string | |
| value? | string | |
| form? | string | |
| formAction? | string | |
| formMethod? | string | |
| formEncType? | string | |
| formNoValidate? | boolean | |
| formTarget? | string | |
| disabled? | boolean | |
| pending? | boolean | |
| pendingLabel? | RemixNode | |
| variant? | Exclude<BtnVariant, 'quiet'> | |
| size? | BtnSize | |
| mix? | MixInput |
Stat · 7 props
| prop | type | notes |
|---|---|---|
| label | string | Machine-register caption above the value ("deploys today"). |
| value | RemixNode | The reading. Digits get tabular-nums by construction. |
| unit? | string | Quiet unit beside the value ("ms", "req/s"). |
| delta? | string | Change chip ("+12%", "−40ms") — rendered as a Badge. |
| deltaTone? | 'pass' | 'fail' | 'warn' | 'neutral' | Tone for the delta. The APP judges good/bad — a falling number is good for latency and bad for revenue, so no sign-sniffing here. |
| hint? | string | Muted context line under the value ("vs. yesterday"). |
| mix? | MixInput |
Steps · 5 props
| prop | type | notes |
|---|---|---|
| steps | Step[] | |
| current | number | 0-based index of the current step. |
| label? | string | Accessible name for the nav. Default 'Steps'. |
| orientation? | 'horizontal' | 'vertical' | 'horizontal' (default) or 'vertical' for tall sidebars. |
| mix? | MixInput |
Submenu · 6 props
| prop | type | notes |
|---|---|---|
| label | string | The trigger row's label. |
| menuLabel? | string | Accessible name for the CHILD menu (defaults to `label`). |
| disabled? | boolean | |
| searchValue? | string | string[] | Typeahead aliases — additive to the label (kit law). |
| children? | RemixNode | MenuItem children of the child menu. |
| mix? | MixInput |
Tab · 4 props
| prop | type | notes |
|---|---|---|
| name | string | Tab name, matched by the TabPanel with the same name. |
| disabled? | boolean | |
| children? | RemixNode | |
| mix? | MixInput |
Table · 7 props
| prop | type | notes |
|---|---|---|
| caption | string | Accessible table name, rendered as a real sr-only `<caption>` and referenced by the scroll region via `aria-labelledby`. Required. |
| id? | string | Stable caption id when the table shares a document with separately rendered fragments. |
| head | Array<RemixNode | SortableHeader> | `<th scope="col">` cell contents; the kit renders the header cells. A `{ label, sort }` entry renders a sortable-column link with `aria-sort`. |
| children? | RemixNode | `<tr>` rows (apps author the `<td>` cells; the kit styles them). |
| dense? | boolean | Tighter cell padding for data-dense tables. |
| stickyHead? | boolean | Header row sticks while the page scrolls (long tables). |
| mix? | MixInput | Composition passthrough for the scroll wrapper. |
TabList · 3 props
| prop | type | notes |
|---|---|---|
| label | string | Accessible name for the tablist (what the group of views is). |
| children? | RemixNode | |
| mix? | MixInput |
TabPanel · 3 props
| prop | type | notes |
|---|---|---|
| name | string | Name of the Tab this panel belongs to. |
| children? | RemixNode | |
| mix? | MixInput |
Tabs · 6 props
| prop | type | notes |
|---|---|---|
| defaultActiveTab? | string | Uncontrolled: the tab active on first render. |
| activeTab? | string | Controlled: the active tab name (pair with onActiveTabChange). |
| onActiveTabChange? | (activeTab: string) => void | |
| disabled? | boolean | Disables every tab. |
| children? | RemixNode | TabList + Tab children, then TabPanel siblings. |
| mix? | MixInput | Composition passthrough for the root element. |
TagInput · 7 props
| prop | type | notes |
|---|---|---|
| label | string | |
| name | string | Form field name — repeated per chip, plus the entry input. |
| defaultValue? | string[] | Initial tags (server-known). |
| placeholder? | string | |
| hint? | string | Machine-register hint; default explains the keys. |
| removeLabel? | (tag: string) => string | i18n: per-chip remove label (default `Remove ${tag}`). |
| mix? | MixInput |
TextareaField · 17 props
| prop | type | notes |
|---|---|---|
| label | string | |
| name | string | |
| id? | string | |
| error? | string | null | |
| hint? | RemixNode | |
| required? | boolean | |
| disabled? | boolean | |
| placeholder? | string | |
| defaultValue? | string | |
| autoComplete? | string | |
| rows? | number | |
| minLength? | number | |
| maxLength? | number | |
| readOnly? | boolean | |
| spellCheck? | boolean | |
| autoSize? | boolean | Let supporting browsers grow the textarea with its content. |
| mix? | MixInput |
Time · 3 props
| prop | type | notes |
|---|---|---|
| value | number | The moment, as epoch ms (what sqlite integers give you). |
| now | number | "Now" at render — pass ONE value per page so rows agree. |
| mix? | MixInput |
Timeline · 3 props
| prop | type | notes |
|---|---|---|
| events | TimelineEvent[] | |
| label? | string | Accessible name for the feed ("Deploy activity"). |
| mix? | MixInput |
Toaster · 3 props
| prop | type | notes |
|---|---|---|
| corner? | 'bottom-right' | 'top-right' | 'bottom-left' | 'top-left' | Stack corner (default 'bottom-right'). |
| regionLabel? | string | i18n: region name (default 'Notifications'). |
| dismissLabel? | string | i18n: per-toast dismiss label (default 'Dismiss notification'). |
Toggle · 8 props
| prop | type | notes |
|---|---|---|
| label | RemixNode | Visible label text, to the control's right. |
| name | string | Form field name. |
| value? | string | Submitted value when checked (browser default `"on"`). |
| defaultChecked? | boolean | Initial checked state (uncontrolled). |
| required? | boolean | Marks the control required (native `required`). |
| disabled? | boolean | Disables the control. |
| hint? | string | Assistive hint under the label; joins `aria-describedby`. |
| mix? | MixInput | Composition passthrough for the wrapping `<label>`. |
ToggleButton · 7 props
| prop | type | notes |
|---|---|---|
| children? | RemixNode | |
| defaultPressed? | boolean | |
| onPressedChange? | (pressed: boolean) => void | Fires after each flip with the new state. |
| label? | string | Accessible name when children are glyph-only. |
| size? | BtnSize | |
| disabled? | boolean | |
| mix? | MixInput |
ToggleGroup · 12 props
| prop | type | notes |
|---|---|---|
| label | string | |
| name | string | |
| options | ToggleGroupOption[] | |
| type | 'single' | 'multiple' | |
| defaultValue? | string | string[] | |
| orientation? | 'row' | 'stack' | |
| size? | 'sm' | 'md' | |
| hint? | RemixNode | |
| error? | string | null | |
| required? | boolean | Marks the native radio group required. Ignored when `type` is `multiple`. |
| disabled? | boolean | |
| mix? | MixInput |
Toolbar · 4 props
| prop | type | notes |
|---|---|---|
| label | string | Accessible name for the set of controls. |
| orientation? | 'horizontal' | 'vertical' | |
| children? | RemixNode | |
| mix? | MixInput |
Tooltip · 5 props
| prop | type | notes |
|---|---|---|
| content | string | |
| trigger | (attrs: TooltipTriggerAttrs) => RemixNode | Render the actual named trigger and apply `ariaDescribedBy` to it. |
| side? | 'top' | 'right' | 'bottom' | 'left' | |
| delay? | number | Pointer delay in milliseconds. Keyboard focus is immediate. Default 800. |
| mix? | MixInput |
UploadList · 3 props
| prop | type | notes |
|---|---|---|
| label | string | Accessible name for the native list. |
| items | UploadListItem[] | |
| mix? | MixInput |