Skip to content
kapable

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.

A deliberately balanced heading for a narrow component container

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).

graphite
deploys today
12+3
72%
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
A one-off reveal on native details — working right here in this static artifact, because the browser owns it.
<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}`} />

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.

The active panel. Arrow keys move, Home/End jump, disabled tabs are skipped.
<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>

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 wrapper

Toaster

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' })  // sticky

Dialog

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.

Removes deploy #4821 from the log. This cannot be undone.
<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).

The petrol theme as custom properties, typed t.* refs, semantic status hues, and the theme stylesheet — lockstep-tested against kapable-theme.css.

<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.

Supplementary help appears without moving focus
<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.

  1. Deploy recorded14:02

    launchpad → production

  2. Build started14:00
  3. 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.

deploys today
12+3

vs. yesterday

p95 latency
184ms−40ms
failures
2+2
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.

deploys / day
12.4k+3
72%
<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.

42%
3 of 8
68%
<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.

neutralaccentpasswarnfailpending
<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.

Sample deploys
appstatuswhen
launchpadpass2m ago
consolewarn1h ago
<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.

Deployments
selectappstatusregion
launchpadreadyiad
consolebuildinglhr
<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.

Ada Lovelaceada@example.devOps Botservice accountGrace Hopper
<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.

ALGHKJAEMJ
Loading
<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 · circle

UploadList

Presentational native list for file names, sizes, transfer status, progress, errors, and app-owned actions.

  • release-notes.pdf1.5 KBuploading
    42%
  • diagram.svg512 Bfailed
<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

launchpad is live on production.

Deploy failed

The build exited non-zero. Check the log, then retry.
Beta channel — pin exact versions before relying on this.
Waiting for the runner to pick this up.
A neutral note, for context that carries no state.
<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.

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.

Environment

Pick where this deploys.

Log level
<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.

Notify through

Choose any channels.

<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.

View
Density
<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.

Cadence

Choose one schedule.

<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.

<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.

Notifications

A labelled group of Fields.

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
Accordion props
proptypenotes
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?booleanSingle mode: allow closing the open item (default true — a set you cannot fully close is a trap).
disabled?booleanDisables every item.
children?RemixNodeAccordionItem children.
mix?MixInput
AccordionItem · 6 props
AccordionItem props
proptypenotes
valuestringIdentity within the set (matches defaultValue).
titleRemixNodeTrigger 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?RemixNodePanel content.
mix?MixInput
AlertDialog · 8 props
AlertDialog props
proptypenotes
idstringDocument-unique id — openers target it via `onOpenDialog(id)`.
titlestringDialog title; renders as the labelling heading.
descriptionstringExplicit decision context, wired to `aria-describedby`.
children?RemixNodeAdditional detail. App forms belong here, not around the dialog.
actions?RemixNodeApp-owned action controls or forms, rendered after Cancel.
cancelLabel?stringCancel button text (default 'Cancel').
width?'sm' | 'md'Dialog width: 'sm' 360px · 'md' 480px (default).
mix?MixInput
Avatar · 4 props
Avatar props
proptypenotes
namestringPerson/entity name — the accessible label, and the initials source.
src?stringImage 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
AvatarGroup props
proptypenotes
peopleAvatarGroupPerson[]
max?numberDiscs shown before the `+N` overflow (default 4).
label?stringGroup accessible name (default "N people").
size?'sm' | 'md''sm' 24px (default — this is a cell component) · 'md' 32px.
mix?MixInput
Badge · 3 props
Badge props
proptypenotes
children?RemixNode
tone?BadgeTone
mix?MixInput
Breadcrumbs · 4 props
Breadcrumbs props
proptypenotes
itemsBreadcrumbItem[]
label?stringAccessible name for the nav. Default 'Breadcrumb'.
separator?RemixNodeSeparator between crumbs (aria-hidden). Default: a mono slash.
mix?MixInput
Btn · 18 props
Btn props
proptypenotes
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?booleanExternally controlled submission state. Disables the button and sets aria-busy.
pendingLabel?RemixNodeVisible replacement while pending; defaults to the original children.
mix?MixInput
Callout · 5 props
Callout props
proptypenotes
children?RemixNode
title?stringBold 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
Card props
proptypenotes
children?RemixNode
pad?CardPad
raised?boolean
mix?MixInput
CheckboxGroup · 9 props
CheckboxGroup props
proptypenotes
labelstring
namestring
optionsCheckboxOption[]
defaultValue?string[]
disabled?boolean
hint?RemixNode
error?string | null
orientation?'stack' | 'row'
mix?MixInput
CodeBlock · 6 props
CodeBlock props
proptypenotes
codestringThe code, verbatim.
tokens?CodeToken[]Optional server-rendered tokens. Falls back to `code` if their text differs.
lang?stringLanguage hint exposed as a data attribute.
label?stringAccessible name for the scroll region. Default 'Code'.
dense?booleanTighter padding + smaller type for dense listings.
mix?MixInput
ComboboxMulti · 12 props
ComboboxMulti props
proptypenotes
labelstring
namestring
optionsComboboxMultiOption[]
defaultValue?string[]
inputId?string
placeholder?string
hint?string
disabled?boolean
selectedLabel?string
allSelectedPlaceholder?string
removeLabel?(option: ComboboxMultiOption) => string
mix?MixInput
CommandK · 1 prop
CommandK props
proptypenotes
targetstringThe Dialog/CommandPalette id to toggle.
CommandPalette · 5 props
CommandPalette props
proptypenotes
idstringDocument-unique id — open with `onOpenDialog(id)`.
commandsCommand[]
title?stringDialog title (default "Commands") — the palette's accessible name.
placeholder?string
mix?MixInput
ContextMenu · 4 props
ContextMenu props
proptypenotes
labelstringAccessible name for the menu ("Deploy row actions").
menuRemixNodeThe MenuItems (plus Separators/Submenus) of the context menu.
children?RemixNodeThe region the right-click (or keyboard menu key) targets.
mix?MixInput
CopyButton · 7 props
CopyButton props
proptypenotes
textstringThe text that lands on the clipboard.
label?stringAccessible/visible label (default "Copy").
announce?stringToast message on success (default "Copied.").
glyphOnly?booleanShow only the glyph (label stays for AT).
variant?BtnVariant
size?BtnSize
mix?MixInput
DataTable · 11 props
DataTable props
proptypenotes
captionstring
columnsDataTableColumn[]
rowsDataTableRow[]
selectionName?stringAdds one native row-selection checkbox per row under this form field name.
selectionHeading?RemixNodeLocalized heading for the optional selection column.
emptyTitle?string
emptyHint?string
emptyHeading?'h2' | 'h3' | 'h4'
dense?boolean
stickyHead?boolean
mix?MixInput
DateField · 15 props
DateField props
proptypenotes
labelstring
namestring
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
DescriptionList props
proptypenotes
itemsDescriptionItem[]
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
Dialog props
proptypenotes
idstringDocument-unique id — openers target it via `onOpenDialog(id)`.
titlestringDialog title; renders as the labelling heading.
children?RemixNodeBody content. Put app forms here, not in `actions`.
actions?RemixNodeFooter 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?stringAccessible name for the × control (i18n; default 'Close').
mix?MixInput
DiffBlock · 4 props
DiffBlock props
proptypenotes
linesDiffLine[]
label?stringAccessible name for the scroll region ("Change to router.ts").
dense?boolean
mix?MixInput
Disclosure · 4 props
Disclosure props
proptypenotes
summaryRemixNodeThe always-visible summary line.
defaultOpen?booleanOpen on first render (`open` attribute — still toggleable).
children?RemixNodeThe revealed content.
mix?MixInput
Drawer · 8 props
Drawer props
proptypenotes
idstringDocument-unique id — openers target it via `onOpenDialog(id)`.
titlestring
children?RemixNode
actions?RemixNodeFooter row; `<form method="dialog">` buttons close natively.
side?'right' | 'left' | 'bottom'Edge the panel pins to (default 'right'). 'bottom' is the sheet.
width?stringPanel width for left/right (default '380px'; clamped). Ignored for bottom.
closeLabel?stringAccessible name for the × control (i18n; default 'Close').
mix?MixInput
Dropzone · 8 props
Dropzone props
proptypenotes
labelstringVisible label ("Attachments").
namestringForm field name.
accept?string`accept` passthrough ("image/png,.pdf").
multiple?boolean
hint?stringMachine-register constraint line ("png or pdf, up to 5 MB").
listLabel?stringi18n: name for the chosen-files list (default 'Chosen files').
removeLabel?(name: string) => stringi18n: per-file remove label (default `Remove ${name}`).
mix?MixInput
EmptyState · 7 props
EmptyState props
proptypenotes
titlestring
hint?string
icon?RemixNode
action?RemixNode
quiet?booleanSans-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
ErrorSummary props
proptypenotes
errorsFormError[]Renders nothing when empty — safe to render unconditionally.
title?stringHeading (default "There is a problem").
mix?MixInput
ExternalLink · 4 props
ExternalLink props
proptypenotes
hrefstring
children?RemixNode
newTab?booleanOpt-in target="_blank" (adds rel="noopener noreferrer").
mix?MixInput
Field · 16 props
Field props
proptypenotes
labelstringVisible label text.
namestringForm field name (also forwarded to custom controls).
id?stringExplicit control id — set it when an ErrorSummary link needs a stable anchor ("#email"); otherwise the render owns an internal id.
error?string | nullError message; renders in `status.fail` and sets `aria-invalid` when a non-empty string.
hint?RemixNodeAssistive hint; renders in `text-muted` and joins `aria-describedby`. Takes any node — the Search composition puts a `<Kbd>` in here.
prefix?RemixNodeAdornment 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?RemixNodeAdornment inside the field after the input ("ms", "@example.dev").
required?booleanMarks the control required (native `required` + a quiet visual marker).
disabled?booleanDisables the default control.
inputType?stringDefault control's input type. `'text'` by default.
placeholder?stringDefault control placeholder.
defaultValue?stringDefault control initial value (uncontrolled).
autoComplete?stringDefault control `autocomplete` token.
children?(attrs: FieldControlAttrs) => RemixNodeCustom control render prop. Receives the wiring attrs and OWNS the control; when provided, Field renders no default input.
control?(attrs: FieldControlAttrs) => RemixNodeSame 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?MixInputComposition passthrough for the wrapping element.
FormActions · 4 props
FormActions props
proptypenotes
children?RemixNode
align?'start' | 'end' | 'between'Horizontal distribution. Default `end`.
responsive?booleanStack actions at the mobile page breakpoint. Default true.
mix?MixInput
FormSection · 4 props
FormSection props
proptypenotes
legendstringThe group name — a real `<legend>`, announced with each control.
hint?stringQuiet context line under the legend.
children?RemixNode
mix?MixInput
IconButton · 18 props
IconButton props
proptypenotes
labelstringAccessible name for the icon-only control.
childrenRemixNodeIcon 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?booleanExternally controlled submission state.
pendingLabel?stringAccessible name while pending; defaults to `label`.
title?string
mix?MixInput
Identity · 6 props
Identity props
proptypenotes
namestring
meta?RemixNodeQuiet mono sub-line under the name. Omit for the single-line form.
src?stringAvatar image URL — omit for the initials disc.
size?'sm' | 'md''sm' 24px avatar (dense cells) · 'md' 32px (default).
href?stringMakes the whole row a single quiet link.
mix?MixInput
InputOTP · 15 props
InputOTP props
proptypenotes
labelstring
namestring
lengthnumber
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
Kbd props
proptypenotes
children?RemixNode
mix?MixInput
LivePill · 3 props
LivePill props
proptypenotes
eventsHrefstringHref 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?numberFeed version at server render (`live.version()`) — closes the SSR→stream-open missed-mutation window.
Masthead · 5 props
Masthead props
proptypenotes
titlestringThe screen's h1. Display serif by default — the arrival moment.
readout?RemixNodeMono readout line under the title — REAL facts (version, sha, counts).
wordmark?stringLowercase mono wordmark above the title ("kapable").
quiet?booleanSans 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
Menu props
proptypenotes
labelstringAccessible name for the menu ("Row actions", "View").
trigger?RemixNodeTrigger content. Defaults to the label text; pass a glyph ("⋯") for icon triggers — the label still names the control for AT.
variant?BtnVariantTrigger button look (default quiet — menus are secondary chrome).
size?BtnSize
children?RemixNodeMenuItem children (plus kit Separators between groups).
disabled?boolean
mix?MixInput
MenuItem · 8 props
MenuItem props
proptypenotes
namestringSelection 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
NumberField props
proptypenotes
labelstring
namestring
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?RemixNodeUnit shown after the value; decorative, so include it in label/hint too.
mix?MixInput
PageShell · 4 props
PageShell props
proptypenotes
children?RemixNode
width?'narrow' | 'md' | 'wide'Column width: 'narrow' 720px · 'md' 880px (default) · 'wide' 1100px.
skipLink?booleanRender the skip link here (only when the Document doesn't own one).
mix?MixInput
Pagination · 9 props
Pagination props
proptypenotes
pagenumberCurrent page, 1-based (clamped).
pageCountnumber
href(page: number) => stringBuilds the href for a page number, e.g. (p) => `/deploys?page=${p}`.
label?stringAccessible name for the nav. Default 'Pagination'.
window?numberNumbers kept around the current page (default 1).
compact?boolean‹ page N of M › — no number strip.
prevLabel?stringi18n: default 'Previous page' / 'Next page'.
nextLabel?string
mix?MixInput
PasswordField · 10 props
PasswordField props
proptypenotes
labelstring
namestring
id?stringExplicit control id (ErrorSummary anchor hook), like Field's.
error?string | null
hint?string
required?boolean
placeholder?string
autoComplete?stringDefault '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
Popover props
proptypenotes
labelstringAccessible name; the trigger text unless `trigger` is given.
trigger?RemixNodeCustom trigger content (glyphs get named by `label`).
variant?BtnVariantTrigger 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?RemixNodePanel content.
disabled?boolean
mix?MixInput
Progress · 7 props
Progress props
proptypenotes
valuenumberCurrent value, clamped to [0, max].
max?numberUpper bound (default 100).
labelstringAccessible name — WHAT is progressing ("Upload", "Build launchpad").
readout?boolean | stringRight-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
RadioGroup props
proptypenotes
labelstringGroup label — rendered as the real `<legend>`.
namestringShared form field name.
optionsRadioOption[]
defaultValue?stringValue checked on first render (uncontrolled).
required?booleanMarks the group required (native `required` on its radios).
disabled?booleanDisables every option.
hint?stringGroup-level assistive hint; joins `aria-describedby`.
error?string | nullGroup-level error; `status.fail`, `role="alert"`, sets `aria-invalid`.
orientation?'stack' | 'row''stack' (default) or 'row' — row wraps on narrow screens.
mix?MixInputComposition passthrough for the fieldset.
RangeField · 11 props
RangeField props
proptypenotes
labelstring
namestring
id?string
error?string | null
hint?RemixNode
disabled?boolean
min?number
max?number
step?number | 'any'
defaultValue?number
mix?MixInput
SearchField · 12 props
SearchField props
proptypenotes
labelstring
namestring
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
SectionLabel props
proptypenotes
labelstring
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
SegmentedControl props
proptypenotes
labelstringAccessible group name (sr-only legend — segments are self-evident chrome).
namestringShared form field name.
optionsSegmentedOption[]
defaultValue?stringValue 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
Separator props
proptypenotes
orientation?'horizontal' | 'vertical''horizontal' (default, a real <hr>) or 'vertical' (a separator span).
mix?MixInput
SideNav · 4 props
SideNav props
proptypenotes
groupsSideNavGroup[]
label?stringAccessible name for the nav (default 'Sections').
width?stringRail width (default '220px').
mix?MixInput
Skeleton · 4 props
Skeleton props
proptypenotes
shape?'line' | 'block' | 'circle''line' (text-height bar, default) · 'block' · 'circle'.
width?stringCSS width (default: line 100%, block 100%, circle 32px).
height?stringCSS height (default: line 12px, block 64px, circle = width).
mix?MixInput
Sparkline · 7 props
Sparkline props
proptypenotes
valuesnumber[]The series, oldest first. One value draws just the "now" dot.
labelstringAccessible summary — say what the trend MEANS, not the numbers ("Deploys per day over two weeks, rising").
tone?SparklineToneLine hue; the app judges tone (default 'neutral').
fill?booleanTranslucent area fill under the line (default false — lines, not fills).
width?numberRendered size in px (defaults 96×28 — a word, not a figure).
height?number
mix?MixInput
Spinner · 3 props
Spinner props
proptypenotes
label?stringAdds status semantics and supplies the accessible announcement.
size?'sm' | 'md' | 'lg'
mix?MixInput
SplitButton · 18 props
SplitButton props
proptypenotes
primaryLabelstring
menuLabelstring
childrenRemixNode
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
Stat props
proptypenotes
labelstringMachine-register caption above the value ("deploys today").
valueRemixNodeThe reading. Digits get tabular-nums by construction.
unit?stringQuiet unit beside the value ("ms", "req/s").
delta?stringChange 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?stringMuted context line under the value ("vs. yesterday").
mix?MixInput
Steps · 5 props
Steps props
proptypenotes
stepsStep[]
currentnumber0-based index of the current step.
label?stringAccessible name for the nav. Default 'Steps'.
orientation?'horizontal' | 'vertical''horizontal' (default) or 'vertical' for tall sidebars.
mix?MixInput
Submenu · 6 props
Submenu props
proptypenotes
labelstringThe trigger row's label.
menuLabel?stringAccessible name for the CHILD menu (defaults to `label`).
disabled?boolean
searchValue?string | string[]Typeahead aliases — additive to the label (kit law).
children?RemixNodeMenuItem children of the child menu.
mix?MixInput
Tab · 4 props
Tab props
proptypenotes
namestringTab name, matched by the TabPanel with the same name.
disabled?boolean
children?RemixNode
mix?MixInput
Table · 7 props
Table props
proptypenotes
captionstringAccessible table name, rendered as a real sr-only `<caption>` and referenced by the scroll region via `aria-labelledby`. Required.
id?stringStable caption id when the table shares a document with separately rendered fragments.
headArray<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?booleanTighter cell padding for data-dense tables.
stickyHead?booleanHeader row sticks while the page scrolls (long tables).
mix?MixInputComposition passthrough for the scroll wrapper.
TabList · 3 props
TabList props
proptypenotes
labelstringAccessible name for the tablist (what the group of views is).
children?RemixNode
mix?MixInput
TabPanel · 3 props
TabPanel props
proptypenotes
namestringName of the Tab this panel belongs to.
children?RemixNode
mix?MixInput
Tabs · 6 props
Tabs props
proptypenotes
defaultActiveTab?stringUncontrolled: the tab active on first render.
activeTab?stringControlled: the active tab name (pair with onActiveTabChange).
onActiveTabChange?(activeTab: string) => void
disabled?booleanDisables every tab.
children?RemixNodeTabList + Tab children, then TabPanel siblings.
mix?MixInputComposition passthrough for the root element.
TagInput · 7 props
TagInput props
proptypenotes
labelstring
namestringForm field name — repeated per chip, plus the entry input.
defaultValue?string[]Initial tags (server-known).
placeholder?string
hint?stringMachine-register hint; default explains the keys.
removeLabel?(tag: string) => stringi18n: per-chip remove label (default `Remove ${tag}`).
mix?MixInput
TextareaField · 17 props
TextareaField props
proptypenotes
labelstring
namestring
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?booleanLet supporting browsers grow the textarea with its content.
mix?MixInput
Time · 3 props
Time props
proptypenotes
valuenumberThe moment, as epoch ms (what sqlite integers give you).
nownumber"Now" at render — pass ONE value per page so rows agree.
mix?MixInput
Timeline · 3 props
Timeline props
proptypenotes
eventsTimelineEvent[]
label?stringAccessible name for the feed ("Deploy activity").
mix?MixInput
Toaster · 3 props
Toaster props
proptypenotes
corner?'bottom-right' | 'top-right' | 'bottom-left' | 'top-left'Stack corner (default 'bottom-right').
regionLabel?stringi18n: region name (default 'Notifications').
dismissLabel?stringi18n: per-toast dismiss label (default 'Dismiss notification').
Toggle · 8 props
Toggle props
proptypenotes
labelRemixNodeVisible label text, to the control's right.
namestringForm field name.
value?stringSubmitted value when checked (browser default `"on"`).
defaultChecked?booleanInitial checked state (uncontrolled).
required?booleanMarks the control required (native `required`).
disabled?booleanDisables the control.
hint?stringAssistive hint under the label; joins `aria-describedby`.
mix?MixInputComposition passthrough for the wrapping `<label>`.
ToggleButton · 7 props
ToggleButton props
proptypenotes
children?RemixNode
defaultPressed?boolean
onPressedChange?(pressed: boolean) => voidFires after each flip with the new state.
label?stringAccessible name when children are glyph-only.
size?BtnSize
disabled?boolean
mix?MixInput
ToggleGroup · 12 props
ToggleGroup props
proptypenotes
labelstring
namestring
optionsToggleGroupOption[]
type'single' | 'multiple'
defaultValue?string | string[]
orientation?'row' | 'stack'
size?'sm' | 'md'
hint?RemixNode
error?string | null
required?booleanMarks the native radio group required. Ignored when `type` is `multiple`.
disabled?boolean
mix?MixInput
Toolbar · 4 props
Toolbar props
proptypenotes
labelstringAccessible name for the set of controls.
orientation?'horizontal' | 'vertical'
children?RemixNode
mix?MixInput
Tooltip · 5 props
Tooltip props
proptypenotes
contentstring
trigger(attrs: TooltipTriggerAttrs) => RemixNodeRender the actual named trigger and apply `ariaDescribedBy` to it.
side?'top' | 'right' | 'bottom' | 'left'
delay?numberPointer delay in milliseconds. Keyboard focus is immediate. Default 800.
mix?MixInput
UploadList · 3 props
UploadList props
proptypenotes
labelstringAccessible name for the native list.
itemsUploadListItem[]
mix?MixInput