Menu
Public repository / 91bd2a37654909c5a3aef6ddd614991bd378ccb8
AkurAI-Framework / site/content/docs/design-system/components.md
Latest change 2e9c26e6274a8310cc2db17fb9390a7af4ab0913 - feat(site): live component previews + copyable curl install widget by Ólafur Búi Ólafsson
# Component catalog
Every component the AkurAI design system ships or intends to ship, grouped by
job. Each entry names its **intent**, **variants**, **states**, an example of the
**markup** (semantic classes, no framework runtime), **accessibility**
requirements, and the **tokens** it consumes. This is the build spec for the
`crates/css` engine and the frontend layer: a component is "done" when its
markup, states, and a11y here all hold.
> **Status** — ✅ shipped · 🟡 partial · ⬜ planned. Naming convention:
> `block` + `block--modifier` style, but written as separate classes today
> (`btn btn-primary`) to match the existing CSS. Every component is plain
> server-renderable HTML; interactivity prefers native elements
> (`<details>`, `<dialog>`, `:focus-within`) over scripted behaviour.
---
## Actions
### Button ✅
Trigger an action or navigation. The most-used control; gets the most states.
- **Variants** — `btn-primary` (gradient, brand), `btn-secondary` (panel),
`btn-ghost` (transparent), `btn-danger` ⬜ (destructive), `btn-link` ⬜
(text-only). Sizes: `btn-sm` ✅, default, `btn-lg` ⬜.
- **States** — default, `:hover`, `:active` (1px press), `:focus-visible`
(accent ring), `[disabled]`, `is-loading` ⬜ (spinner + label hidden).
- **Markup**
```preview
<a class="btn btn-primary" href="/start">Get started</a>
<button class="btn btn-secondary btn-sm">Save</button>
<button class="btn btn-secondary" disabled>Unavailable</button>
```
- **A11y** — use real `<button>` for actions, `<a>` for navigation; never a
clickable `<div>`. Focus ring is `:focus-visible` only. A loading button keeps
its width and sets `aria-busy="true"`.
- **Tokens** — `--accent`, `--panel-2`, `--border-2`, `--radius-btn`, focus glow.
### Button group ⬜
Join related buttons into one segmented control (e.g. view switch).
- **Variants** — `btn-group` (attached), `btn-group-spaced`.
- **States** — one child may be `is-active` (pressed look).
- **Markup**
```preview
<div class="btn-group" role="group" aria-label="View">
<button class="btn btn-secondary is-active">List</button>
<button class="btn btn-secondary">Grid</button>
</div>
```
- **A11y** — wrap in `role="group"` with a label; the active item carries
`aria-pressed="true"`.
### Icon button ⬜
A square button with only an icon — toolbars, table-row actions.
- **Variants** — `btn-icon`, `btn-icon btn-ghost`, sizes sm/md.
- **A11y** — **mandatory** `aria-label` (no visible text); icon is
`aria-hidden="true"`.
- **Tokens** — `--radius-btn`, `--muted` (idle), `--fg` (hover).
### Dropdown menu ⬜
A button that reveals a list of actions or links.
- **Anatomy** — trigger button → floating `menu` panel → `menu-item`s,
`menu-separator`, optional `menu-label`.
- **States** — `open`, item `:hover`/`:focus`, `is-disabled`, `is-destructive`.
- **Markup**
```preview
<details class="dropdown">
<summary class="btn btn-secondary">Actions ▾</summary>
<div class="menu" role="menu">
<a class="menu-item" role="menuitem" href="#">Edit</a>
<button class="menu-item is-destructive" role="menuitem">Delete</button>
</div>
</details>
```
- **A11y** — native `<details>` gives open/close + Esc for free; for full
arrow-key roving focus, enhance progressively. `role="menu"` /
`role="menuitem"` on the panel.
- **Tokens** — `--panel`, `--border`, `--shadow`, `--z-dropdown`.
### Link ✅
Inline navigation. Accent colour, underline on hover.
- **Variants** — default, `link-muted` ⬜, `link-external` ⬜ (trailing ↗).
- **A11y** — descriptive text (never "click here"); external links get
`rel="noopener"` and an announced "(opens in new tab)" when targeted.
---
## Forms & inputs
### Field ⬜
The wrapper that pairs a control with its label, hint, and error — the unit
forms are actually built from.
- **Anatomy** — `field` → `<label>` + control + `field-hint` + `field-error`.
- **States** — default, `is-invalid` (danger border + visible error),
`is-disabled`, `is-required` (mark on label).
- **Markup**
```preview
<div class="field is-invalid">
<label for="email">Email</label>
<input id="email" type="email" aria-describedby="email-err" />
<p class="field-error" id="email-err">Enter a valid address.</p>
</div>
```
- **A11y** — `<label for>` ties to the control; errors link via
`aria-describedby` and set `aria-invalid="true"`; required is real `required`
plus a visual mark.
### Text input ✅
Single-line text. Base for email, password, search, number, url.
- **Variants** — `text`/`email`/`password`/`number`/`url`; sizes sm/md;
`input-group` ⬜ with prefix/suffix add-ons.
- **States** — default, `:focus` (accent ring), `:disabled`, `[readonly]`,
`is-invalid`, with-placeholder.
- **Markup**
```preview
<input type="email" placeholder="ada@example.com" />
```
- **A11y** — always labelled (visible `<label>` or `aria-label`); placeholder is
never the label. `:focus` ring via box-shadow.
- **Tokens** — `--bg`, `--border`, `--accent`, `--radius-sm`.
### Textarea ✅
Multi-line text; vertical resize only.
- **States** — same as text input; `rows` sets initial height.
### Select ✅
Native single choice. Styled to match inputs.
- **Variants** — `select`, `select-sm`; multi-select ⬜.
- **A11y** — keep the native `<select>` for keyboard + screen-reader support;
only restyle the box, never replace with a div-listbox unless the Combobox is
used.
### Combobox / Autocomplete ⬜
Type-to-filter select over many options; the bridge to search.
- **Anatomy** — input → `listbox` popup → `option`s, with an empty state.
- **States** — open, filtering, active-descendant highlighted, no-results,
loading (async).
- **A11y** — `role="combobox"` + `aria-expanded` + `aria-activedescendant`;
arrow keys move the active option, Enter selects, Esc closes.
### Checkbox ✅
Independent boolean(s).
- **States** — unchecked, checked, `indeterminate` ⬜ (parent of a partial set),
disabled, focus ring.
- **Markup**
```preview
<label class="inline"><input type="checkbox" checked /> Subscribe</label>
```
- **A11y** — native input; indeterminate set via the DOM property and
`aria-checked="mixed"`. Tokens: `accent-color: var(--accent)`.
### Radio ✅
One choice from a small set.
- **A11y** — group in a `<fieldset>` with `<legend>`; arrow keys move within the
group (native behaviour).
### Switch / Toggle ⬜
Instant on/off for settings (no Save needed).
- **States** — off, on, disabled, focus.
- **Markup**
```preview
<label class="switch">
<input type="checkbox" role="switch" />
<span class="switch-track"></span> Email notifications
</label>
```
- **A11y** — it is a checkbox with `role="switch"`; label describes the setting,
not the state. Distinguish from checkbox: a switch applies immediately.
### Range / Slider ✅
Pick a number along a continuum.
- **Variants** — single value ✅, dual-handle range ⬜, with tick marks ⬜.
- **A11y** — native `<input type="range">`; pair with a visible value readout.
### File upload ⬜
Select or drop files. Maps to the framework's upload endpoint.
- **Anatomy** — drop zone → hidden file input → selected-file list with progress
+ remove.
- **States** — idle, `is-dragover`, uploading (per-file progress), success,
error (too big / wrong type).
- **A11y** — a real `<input type="file">` behind the styled zone; drag-drop is an
enhancement, never the only path. Announce upload status via a live region.
### Date / time picker ⬜
Pick a date, time, or range.
- **Variants** — date, datetime, range; native-first.
- **A11y** — start from `<input type="date">`; a custom calendar must support
full keyboard grid navigation and announce the focused day.
### Search input ⬜
Text input specialised for search: leading icon, clear button, `type="search"`.
- **States** — empty, typing, with-clear, loading, with-results-count.
### Form layout ⬜
Arrange fields: `form-grid` (responsive columns, `span-2` to span) ✅ for
demos, plus `form-section` ⬜ (titled group) and `form-actions` ⬜ (footer button
row, right-aligned).
---
## Data display
### Card ✅
A grouped surface for one entity or summary.
- **Variants** — `card` ✅, `card-interactive` ⬜ (hover lift, whole-card link),
`card-media` ⬜ (image header). Slots: title, body, `card-footer` ⬜.
- **Markup**
```preview
<div class="card">
<h3>Crash-safe by design</h3>
<p class="muted">Atomic copy-on-write commits.</p>
</div>
```
- **A11y** — a clickable card needs one real link; don't nest interactive
elements inside a card-wide link.
- **Tokens** — `--panel-2`, `--bg-2`, `--border`, `--radius`.
### Table ✅
Tabular rows. The workhorse of the admin UI.
- **Variants** — `table` ✅, `table-striped` ⬜, `table-compact` ⬜,
`table-sticky-head` ⬜.
- **Features (admin) ⬜** — sortable headers, row selection (checkbox column),
row hover, inline actions, sticky first column, resize.
- **States** — hover row, selected row, sorted column (asc/desc), loading
(skeleton rows), empty (empty-state slot).
- **Markup**
```preview
<table class="table">
<thead><tr><th>Id</th><th>Title</th></tr></thead>
<tbody><tr><td>01</td><td>Hello</td></tr></tbody>
</table>
```
- **A11y** — real `<table>`/`<th scope>`; sortable headers are `<button>`s
inside `<th>` with `aria-sort`. Selection column header has a label.
- **Tokens** — `--border`, `--muted` (head), `--panel-2` (hover/selected).
### Description list ⬜
Key/value detail view for a single record.
- **Markup**
```preview
<dl class="desc-list">
<dt>Status</dt><dd><span class="badge badge-ok">Active</span></dd>
<dt>Created</dt><dd>2026-06-25</dd>
</dl>
```
### Stat / Metric ✅
A single headline number with a label.
- **Variants** — `stat` ✅, with delta/trend ⬜ (▲ green / ▼ red), with sparkline
⬜.
- **Markup**
```preview
<div class="stat"><div class="stat-value">~660 KB</div><div class="stat-label">Binary size</div></div>
```
### Badge ✅
Tiny status pill — non-interactive.
- **Variants** — `badge-ok` ✅, `badge-muted` ✅, `badge-warn`/`badge-info`/
`badge-danger` ⬜, `badge-dot` ⬜ (leading status dot).
- **A11y** — colour is never the only signal; the text carries the meaning.
### Tag / Chip ⬜
Like a badge but interactive — removable filters, multi-select tokens.
- **Variants** — static, removable (× button), selectable.
- **A11y** — the remove control is a real `<button>` with
`aria-label="Remove X"`.
### Avatar ⬜
Represent a user or entity.
- **Variants** — image, initials fallback, sizes xs–lg, `avatar-group`
(overlapped stack with +N overflow), status-dot overlay.
- **A11y** — `alt` is the person's name; decorative grouping is `aria-hidden`.
### Code block ✅
Monospace, scrollable code. Used in docs and the install snippet.
- **Variants** — `code-sample` / `install` ✅, with-filename header ⬜,
copy-button ⬜, line-numbers ⬜.
- **Tokens** — `--mono`, `--border`, dark code surface.
### Timeline ⬜
Ordered events down a vertical rail — audit log, record history.
### Tree view ⬜
Nested, expandable hierarchy — collection/field navigation.
- **A11y** — `role="tree"`/`treeitem`/`group` with `aria-expanded`; arrow keys
expand/collapse and move.
---
## Feedback
### Alert / Callout ✅
A persistent, inline message tied to a region of the page.
- **Variants** — `alert-success` ✅, `alert-info` ✅, `alert-warning` ✅,
`alert-danger` ✅; `alert-dismissible` ⬜ (close button), with-icon ⬜,
with-action ⬜.
- **Markup**
```preview
<div class="alert alert-info"><strong>Note.</strong> <span>TLS terminates at the edge.</span></div>
```
- **A11y** — error/success that appears after an action uses `role="alert"`
(assertive); a static informational note does not.
- **Tokens** — semantic colour tinted into `--panel` via `color-mix`.
### Toast / Notification ⬜
A transient, floating message after an action (saved, copied, failed).
- **Variants** — success/info/warning/danger; with action ("Undo"); auto-dismiss
vs sticky.
- **Anatomy** — a `toast-region` (fixed corner) stacks `toast` items.
- **States** — entering, visible, leaving, paused-on-hover.
- **A11y** — the region is an `aria-live="polite"` container (assertive for
errors); never steal focus. Respect `prefers-reduced-motion`.
- **Tokens** — `--panel`, `--shadow`, `--z-toast`, semantic accent.
### Banner ⬜
A full-width, page-level message (system status, trial expiring).
- **Variants** — info/warning/danger; dismissible; sticky-top.
### Progress ⬜
Show completion of a known-length task.
- **Variants** — `progress` (bar), `progress-indeterminate`, `progress-circular`.
- **A11y** — `role="progressbar"` + `aria-valuenow/min/max`.
### Spinner / Loader ⬜
Indeterminate busy indicator for short waits.
- **Variants** — sizes xs–lg; inline (in a button) vs centered (in a panel).
- **A11y** — `role="status"` + visually-hidden "Loading"; CSS-only animation.
### Skeleton ⬜
Greyed placeholders that mimic content shape while loading — preferred over
spinners for tables, cards, and lists.
- **Variants** — text line, avatar, card, table-row; shimmer animation.
- **A11y** — container `aria-busy="true"`; skeletons are `aria-hidden`.
### Empty state ⬜
What a list/table/search shows when there is nothing — first-run, no-results,
error.
- **Anatomy** — icon → heading → one-line explanation → primary action.
- **Markup**
```preview
<div class="empty">
<h3>No records yet</h3>
<p class="muted">Create your first record to get started.</p>
<a class="btn btn-primary">New record</a>
</div>
```
---
## Navigation
### Navbar ✅
Top, sticky, blurred app/site bar: brand + links.
- **Variants** — site nav ✅, app nav (with user menu) ⬜, with mobile toggle ⬜.
- **A11y** — wrap links in `<nav aria-label>`; mark the current link
`aria-current="page"`.
- **Tokens** — `--bg` (translucent), `--border`, `backdrop-filter`, `--z-sticky`.
### Sidebar nav ✅
Vertical, grouped navigation — docs and the admin shell.
- **Variants** — `sidebar` with `sidebar-group` + `sidebar-title` ✅; collapsible
groups ⬜; active-item highlight ⬜ (drive off the `active` slug).
- **A11y** — `<nav aria-label>`; current item `aria-current="page"`.
### Tabs ⬜
Switch between sibling panels in place.
- **Variants** — underline, pill, vertical.
- **States** — active, hover, disabled, focus.
- **A11y** — `role="tablist"`/`tab`/`tabpanel`; arrow keys move between tabs,
`aria-selected` tracks the active one.
### Breadcrumbs ⬜
Show position in a hierarchy.
- **Markup**
```preview
<nav class="breadcrumbs" aria-label="Breadcrumb">
<a href="/">Home</a> <span aria-hidden="true">/</span>
<a href="/posts">Posts</a> <span aria-hidden="true">/</span>
<span aria-current="page">Edit</span>
</nav>
```
### Pagination ⬜
Page through a long result set — paired with the data table.
- **Variants** — numbered, prev/next only, with page-size select, with
result-count.
- **A11y** — `<nav aria-label="Pagination">`; current page `aria-current="page"`;
disabled ends are not focusable.
### Steps / Stepper ⬜
Show progress through a multi-step flow (wizard, onboarding).
- **Variants** — horizontal, vertical; states per step: done, current, upcoming.
### Command palette ⬜
Keyboard-first action/search overlay (⌘K).
- **Anatomy** — overlay → search input → grouped results → keyboard hint footer.
- **A11y** — a Combobox in a modal: traps focus, `aria-activedescendant`,
Esc closes, restores focus to the trigger.
### Anchor / table of contents ⬜
In-page heading nav with scroll-spy for long docs.
---
## Overlays
All overlays share one rule: **trap focus while open, restore it on close,
close on Esc, and label the dialog**. Prefer the native `<dialog>` element.
### Modal / Dialog ⬜
Focused task or confirmation over a dimmed page.
- **Variants** — sizes sm/md/lg; `dialog-confirm` (title + message + 2 buttons);
`dialog-form` (a form inside).
- **Anatomy** — backdrop → `dialog` panel → header (title + close) → body →
footer actions.
- **States** — opening, open, closing; `is-busy` while submitting.
- **Markup**
```preview
<dialog class="modal">
<h2>Delete record?</h2>
<p class="muted">This cannot be undone.</p>
<div class="form-actions">
<button class="btn btn-secondary" value="cancel">Cancel</button>
<button class="btn btn-danger" value="confirm">Delete</button>
</div>
</dialog>
```
- **A11y** — native `<dialog>` gives focus-trap, Esc, and backdrop; set
`aria-labelledby` to the title. Close returns focus to the opener.
- **Tokens** — `--panel`, `--shadow`, `--z-overlay`, backdrop tint.
### Drawer / Sheet ⬜
A panel that slides from an edge — filters, record detail, mobile nav.
- **Variants** — right/left/bottom; sizes; with backdrop or push-content.
- **A11y** — same focus discipline as the modal.
### Popover ⬜
A small floating panel anchored to a trigger — richer than a tooltip, can hold
controls.
- **A11y** — `aria-expanded` on the trigger; Esc + outside-click close.
### Tooltip ⬜
A tiny label on hover/focus for an icon or truncated text.
- **A11y** — `aria-describedby` links trigger to tip; must appear on **focus**,
not hover only; never put essential info or interactive content in a tooltip.
### Context menu ⬜
Right-click actions on a row or item (reuses the dropdown `menu`).
---
## Layout primitives
Low-level building blocks; no chrome of their own.
### Container ✅
Centres content at `max-width: 1080px` with gutters. (`container`.)
### Grid ⬜
Token-gap responsive columns: `grid-2`/`grid-3`/`grid-4`, collapsing at `md`.
Already used ad-hoc (`features`, `stats`, `grid-3`); to be generalised.
### Stack ✅
Vertical rhythm — equal gap between children (`stack`).
### Cluster ⬜
Horizontal wrap with consistent gap — button rows, tag lists, toolbars.
### Split ✅
Two-column, content + media/code, collapsing at `md` (`split`).
### Divider ⬜
A labelled or plain horizontal rule between sections (`divider`).
### Page header ✅
Eyebrow + title + lede + optional action row (`page-head`). The standard top of
every docs and admin page.
### Section ✅
A top-bordered vertical band of content (`comp-cat` pattern), generalised to
`section`.
---
## Admin shell (PocketBase-style) ⬜
The composed surfaces the framework ships so a new project has a working admin UI
on day one. These assemble the primitives above.
### App shell ⬜
The frame: top app bar + collapsible collection sidebar + content area + toast
region. Responsive: sidebar becomes a drawer at `md`.
### Collection sidebar ⬜
Lists collections (from `collections.toml`) with active highlight, search, and a
"new collection" action.
### Record table ⬜
The data table wired to a collection: server-driven columns, sort, the
`?search=` box, row selection, bulk actions, pagination, and an empty state.
### Record editor ⬜
An auto-generated form from a collection's fields — each field type maps to a
control (text→input, bool→switch, relation→combobox, embed-text→textarea), with
inline validation, dirty-state guard, and Save/Cancel in `form-actions`.
### Filter bar ⬜
Compose a query against the records DSL: field + operator + value chips, AND/OR,
saved views.
### Auth / login form ⬜
Centered card: email + password (or magic-link), error alert, submit with loading
state. The front door of every admin install.
### Settings panel ⬜
Tabbed forms for instance settings — sectioned `form-section`s with switches and
inputs, one Save per section.
---
## What "done" means
A component graduates from ⬜ → 🟡 → ✅ when, in order: (1) its markup renders
from the `crates/css` utility/atomic output with no hand-written one-off CSS;
(2) every state listed above is reachable and visible; (3) the accessibility
requirements pass a keyboard-only and screen-reader check; (4) it appears on the
live `/components` page with a usage example. Track status changes here in the
same commit as the implementation, the way the crate map is tracked in
`AGENTS.md`.