docs GitHub

Design systemDraft

Form controls

The nine input controls. Settings, the planner's quick add, the vault editor, question cards and list searches all build from these. Drawn on "Settings · account and project scopes", "Places, Settings", "Project settings, Teammates and usage", "Planner, phone and desktop" and "Session · the composer, like Claude Code".

SurfaceImplementing fileStatus
Deckdeck/css/deck.css .input .sw .seg .search (main); deck/chat/chat.css .cv-chk .cv-radio (work/chat)partial
Appthe text input in apps/app/src/session/Composer.tsx only (work/mobile)partial
LumenSwiftUI TextField and TextEditor in local/capsule/native/Sources/UI/CapsuleView.swift, UI/AgentDeskView.swift (work/capsule-pro)partial

Anatomy

Field wrapper. Column, gap 6: label (meta 12/16 weight 600, --text-2), the control, help (meta 12/16, --label). The help line's slot is reserved, so an error never moves the form.

Field (input). Height 32, padding 0 10, radius --radius-field (8), border 1 px --rule-strong, fill --bg, base 13/18 --text, placeholder --label. Optional leading 16 icon (--label) and trailing icon button, gap 8. Large: height 40, padding 0 12, read 15/22 (the planner's quick add). Codes, keys and paths inside use JetBrains Mono.

Textarea. Padding 10 12, radius --radius-field, border and fill as field, read 15/22. Grows with content to 8 lines, then scrolls inside.

Select. A field with the value and a trailing 12 chev-d, fill --panel. Opens a popover menu on desktop and a sheet on the phone; never the browser's native menu on desktop.

Toggle. Track 32 x 18, radius 9; knob 14, inset 2. Off: track --rule-strong, knob --text. On: track --primary-bg, knob --primary-ink, moved 14.

Segmented control. Container fill --hover, padding 2, gap 2, radius 8. Segments height 28, padding 0 12, radius 6, base 13/18, --text-2. On: fill --panel, ink --text, weight 600, a 1 px --rule-strong ring. Two to four segments; five or more is a select.

Stepper. Fill --hover, radius --radius-button-touch (10): minus icon button, the value (centred, fixed width for three digits), plus icon button. Desktop uses 28 icon buttons around a base 13/18 value.

Checkbox. 16 square, radius 4, 1.5 inset --rule-strong. On: fill --text, a 12 check in --bg. Mixed: fill --text, a 12 minus in --bg.

Radio. 16 circle, 1.5 inset --rule-strong. On: ring --focus, an 8 dot --focus.

Search field. A field with the 16 search icon leading (--label) and, once there is text, a 28 x icon button trailing. Placeholder names what it finds: "Find a setting", "Search projects".

Sizes

ControlDesktopPhone
Field, select, search32 (40 large)44, radius 10, read 17/24 (no zoom on focus)
Textarearead 15/22read 17/24
Toggle32 x 1848 x 27, knob 21, in a 44 row
Segmented28 segments, 32 overallpadding 3, gap 3, radius 10, 38 segments, radius 8, read 17/24, 44 overall
Stepper28 buttons44 tall, 44 buttons, value read 17/24 36 wide
Checkbox, radio1616 drawn; the whole row (min 44) is the target

No input text under 16 px on the phone.

States

  • Hover (pointer). Field border --text-2; segment ink --text; checkbox and radio ring --text-2.
  • Focus. Field: border --focus plus a 1 px --focus ring, no offset outline. Toggle, segment, checkbox, radio, stepper buttons: 2 px outline --focus, offset 2.
  • Disabled. Ink and placeholder --label, border --rule, fill unchanged; a toggle's track --rule, knob --label. Help says why ("Kept per account only").
  • Read-only (a Claude Code file value). No border, fill --hover, ink --text-2, the source chip beside it.
  • Error. Border stays --rule-strong; the help slot shows the failed icon (12) and the reason in --text-2 ("Not saved. The box refused 0 as a limit."). No red, no violet.
  • Saved. Optimistic: the control changes on the tap; "Saved" with a 12 check shows in the reserved slot for 2 s. If the box refuses, the control flips back and the slot reads "Not saved".
  • Offline. Changes queue through the outbox and replay; the slot reads "Queued".
  • Stepper at a limit. That button's ink --label, aria-disabled="true".

Keyboard and touch

  • Tab order follows reading order. Enter in a field submits its form; Esc in a search clears it, a second Esc leaves.
  • Toggle and checkbox: Space. Radio and segmented: arrow keys move and select (roving tabindex).
  • Select: Enter or Space opens; ↑ ↓ move; ⏎ picks; Esc closes. Typing jumps to a match.
  • Stepper: ↑ ↓ on the value, or the buttons; a held button repeats every 120 ms after 400 ms.
  • Touch: every control's hit target is 44; toggling a settings row anywhere toggles it.

Motion

Toggle knob and segment fill over --motion-tap with --ease. Saved line fades in over --motion-reveal. Reduced motion: instant.

Copy

Labels are nouns in sentence case ("Quiet hours", "Mode"). Help is one plain line. Placeholders are examples or verbs, never the label repeated. Never "Invalid input", "Error:", or an exclamation mark.

Accessibility

  • Every control has a visible label or an aria-label; help and error link by aria-describedby.
  • Roles: toggle switch with aria-checked; segmented radiogroup (tabs use tablist, see tabs); stepper spinbutton with min, max and value; search searchbox.
  • Placeholder --label on --bg passes AA at 13; the on toggle --primary-ink on --primary-bg passes in both themes.

Gaps

  • Deck: .input is 40 tall on --panel with a 15 size; use 32 on --bg at 13 (40 is the large field). Focus has no ring.
  • Deck: the toggle .sw is 34 x 20 with an on track of --text; use 32 x 18 and --primary-bg.
  • Deck: .seg has a --rule border and 26 segments with aria-pressed; use the --hover container, 28 segments and radiogroup semantics.
  • Deck: no stepper or select component (native select.input in settings); two checkboxes (.cv-chk on is bone, the design is --text).
  • Deck: .search is 420 wide on --panel with a --rule border; use the field.
  • App: only the composer's text input exists (16 px); build the other eight at touch sizes.
  • Lumen: system TextField and TextEditor styling; no toggles, segments or steppers.
  • System: the stepper is drawn twice (a --hover fill in Settings, a --rule-strong outline in Project settings); this spec takes the fill.