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.

btn-ghost (transparent), btn-danger ⬜ (destructive), btn-link ⬜ (text-only). Sizes: btn-sm ✅, default, btn-lg ⬜.

(accent ring), [disabled], is-loading ⬜ (spinner + label hidden).

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

clickable <div>. Focus ring is :focus-visible only. A loading button keeps its width and sets aria-busy="true".

Button group ⬜

Join related buttons into one segmented control (e.g. view switch).

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

aria-pressed="true".

Icon button ⬜

A square button with only an icon — toolbars, table-row actions.

aria-hidden="true".

Dropdown menu ⬜

A button that reveals a list of actions or links.

menu-separator, optional menu-label.

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

arrow-key roving focus, enhance progressively. role="menu" / role="menuitem" on the panel.

Link ✅

Inline navigation. Accent colour, underline on hover.

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.

is-disabled, is-required (mark on label).

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

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.

input-group ⬜ with prefix/suffix add-ons.

is-invalid, with-placeholder.

<input type="email" placeholder="ada@example.com" />

never the label. :focus ring via box-shadow.

Textarea ✅

Multi-line text; vertical resize only.

Select ✅

Native single choice. Styled to match inputs.

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.

loading (async).

arrow keys move the active option, Enter selects, Esc closes.

Checkbox ✅

Independent boolean(s).

disabled, focus ring.

<label class="inline"><input type="checkbox" checked /> Subscribe</label>

aria-checked="mixed". Tokens: accent-color: var(--accent).

Radio ✅

One choice from a small set.

group (native behaviour).

Switch / Toggle ⬜

Instant on/off for settings (no Save needed).

<label class="switch">
  <input type="checkbox" role="switch" />
  <span class="switch-track"></span> Email notifications
</label>

not the state. Distinguish from checkbox: a switch applies immediately.

Range / Slider ✅

Pick a number along a continuum.

File upload ⬜

Select or drop files. Maps to the framework's upload endpoint.

+ remove.

error (too big / wrong type).

enhancement, never the only path. Announce upload status via a live region.

Date / time picker ⬜

Pick a date, time, or range.

full keyboard grid navigation and announce the focused day.

Search input ⬜

Text input specialised for search: leading icon, clear button, type="search".

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.

card-media ⬜ (image header). Slots: title, body, card-footer ⬜.

<div class="card">
  <h3>Crash-safe by design</h3>
  <p class="muted">Atomic copy-on-write commits.</p>
</div>

elements inside a card-wide link.

Table ✅

Tabular rows. The workhorse of the admin UI.

table-sticky-head ⬜.

row hover, inline actions, sticky first column, resize.

(skeleton rows), empty (empty-state slot).

<table class="table">
  <thead><tr><th>Id</th><th>Title</th></tr></thead>
  <tbody><tr><td>01</td><td>Hello</td></tr></tbody>
</table>

inside <th> with aria-sort. Selection column header has a label.

Description list ⬜

Key/value detail view for a single record.

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

⬜.

<div class="stat"><div class="stat-value">~660 KB</div><div class="stat-label">Binary size</div></div>

Badge ✅

Tiny status pill — non-interactive.

badge-danger ⬜, badge-dot ⬜ (leading status dot).

Tag / Chip ⬜

Like a badge but interactive — removable filters, multi-select tokens.

aria-label="Remove X".

Avatar ⬜

Represent a user or entity.

(overlapped stack with +N overflow), status-dot overlay.

Code block ✅

Monospace, scrollable code. Used in docs and the install snippet.

copy-button ⬜, line-numbers ⬜.

Timeline ⬜

Ordered events down a vertical rail — audit log, record history.

Tree view ⬜

Nested, expandable hierarchy — collection/field navigation.

expand/collapse and move.


Feedback

Alert / Callout ✅

A persistent, inline message tied to a region of the page.

alert-danger ✅; alert-dismissible ⬜ (close button), with-icon ⬜, with-action ⬜.

<div class="alert alert-info"><strong>Note.</strong> <span>TLS terminates at the edge.</span></div>

(assertive); a static informational note does not.

Toast / Notification ⬜

A transient, floating message after an action (saved, copied, failed).

vs sticky.

errors); never steal focus. Respect prefers-reduced-motion.

Banner ⬜

A full-width, page-level message (system status, trial expiring).

Progress ⬜

Show completion of a known-length task.

Spinner / Loader ⬜

Indeterminate busy indicator for short waits.

Skeleton ⬜

Greyed placeholders that mimic content shape while loading — preferred over spinners for tables, cards, and lists.

Empty state ⬜

What a list/table/search shows when there is nothing — first-run, no-results, error.

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

aria-current="page".

Sidebar nav ✅

Vertical, grouped navigation — docs and the admin shell.

groups ⬜; active-item highlight ⬜ (drive off the active slug).

Tabs ⬜

Switch between sibling panels in place.

aria-selected tracks the active one.

Breadcrumbs ⬜

Show position in a hierarchy.

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

result-count.

disabled ends are not focusable.

Steps / Stepper ⬜

Show progress through a multi-step flow (wizard, onboarding).

Command palette ⬜

Keyboard-first action/search overlay (⌘K).

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.

dialog-form (a form inside).

footer actions.

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

aria-labelledby to the title. Close returns focus to the opener.

Drawer / Sheet ⬜

A panel that slides from an edge — filters, record detail, mobile nav.

Popover ⬜

A small floating panel anchored to a trigger — richer than a tooltip, can hold controls.

Tooltip ⬜

A tiny label on hover/focus for an icon or truncated text.

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