Design systemDraft
Icon button
A borderless square with one 16 icon, for actions people use often enough that a word is noise: More, Back, Close, Add, Dictate, Send, Copy, Pause. Drawn on most boards: headers ("Projects, phone and desktop"), the composer bar ("Session · the composer, like Claude Code"), steppers ("Project settings, Teammates and usage").
| Surface | Implementing file | Status |
|---|---|---|
| Deck | deck/css/deck.css .ibtn (main) | built |
| App | none (work/mobile) | not built |
| Lumen | inline SwiftUI buttons with SF Symbols in local/capsule/native/Sources/Extensions/sight/SessionPanel.swift, UI/CapsuleView.swift (work/capsule-pro) | partial |
Anatomy
- A square, no border, fill none, ink
--text-2, one icon centred. - The icon is 16 at 28 and 32, and 20 at 44 (the phone's composer circles and nav buttons). The rail's 20 is its own component.
- Always an accessible name; never a visible label.
Variants
| Variant | Look | Where |
|---|---|---|
| Plain | As above | Headers, rows, composer bar, stepper ends |
| Toggle | aria-pressed="true": fill --hover, ink --text | Pin, mute, show thinking |
| Filled round | 44 circle, fill --hover, ink --text | Phone "New agent", Lumen's mic (40) |
| Send | Plain, fill --hover, ink --text when there is text; the primary colours only when it is the surface's one primary | Composer |
Only one icon button on a surface may take the primary colours, and only when no other primary exists there.
Sizes
| Size | Token | Radius | Where |
|---|---|---|---|
| 28 | --control-xs | --radius-button (8) | Composer bar, dense rows, desktop steppers |
| 32 | --control-sm | --radius-button (8) | Desktop default |
| 44 | --control-touch | --radius-button-touch (10), round for filled | Phone default, phone steppers, back |
On the phone every icon button is 44; a 28 or 32 drawing inside a row still gets a 44 hit area.
States
- Hover (pointer). Fill
--hover, ink--text. - Focus. 2 px outline
--focus, offset 2. - Pressed. Fill
--hover, ink--text, over--motion-tap. - Toggle on. Fill
--hover, ink--text; hover keeps it. - Disabled. Ink
--label, no hover,aria-disabled="true"(a stepper at its limit). - Busy. The spinner (14) replaces the icon; the size never changes.
Keyboard and touch
Tab to it; Enter or Space activates. Toggles announce their state. The desktop tooltip shows the name and the key, if any, after 500 ms of hover (proposed). No long-press menus on icon buttons, except Send, where a long press queues for after the turn (the composer's ⌥⏎).
Motion
Fill and ink over --motion-tap with --ease. Reduced motion: instant.
Copy
aria-label in sentence case, verb first where it is an action: "More", "Back", "Close", "New
session", "New agent", "Dictate", "Send", "Copy code", "Pause", "One fewer teammate", "One more
teammate". Never an icon button for a destructive action; use the hold button.
Accessibility
- Role button with
aria-label; toggles addaria-pressed. Native:accessibilityRole="button"plusaccessibilityLabel; SwiftUI.accessibilityLabel. - Icon ink
--text-2on--bgand--panelholds 3:1 in both themes;--labelis only for disabled.
Gaps
- Deck:
.ibtnradius uses the old--r-2; switch to--radius-button. No 44 size, no filled round variant, no busy. - App: no icon button; screens use
Pressablewith text. BuildIconButtonwith 44 default and the icon component. - Lumen: close and mic buttons are SF Symbols at 11 to 13 pt with no hover fill or focus
ring; draw the set's icons in a 28 square with
Tokenscolours.