docs GitHub

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

SurfaceImplementing fileStatus
Deckdeck/css/deck.css .ibtn (main)built
Appnone (work/mobile)not built
Lumeninline 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

VariantLookWhere
PlainAs aboveHeaders, rows, composer bar, stepper ends
Togglearia-pressed="true": fill --hover, ink --textPin, mute, show thinking
Filled round44 circle, fill --hover, ink --textPhone "New agent", Lumen's mic (40)
SendPlain, fill --hover, ink --text when there is text; the primary colours only when it is the surface's one primaryComposer

Only one icon button on a surface may take the primary colours, and only when no other primary exists there.

Sizes

SizeTokenRadiusWhere
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 filledPhone 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 add aria-pressed. Native: accessibilityRole="button" plus accessibilityLabel; SwiftUI .accessibilityLabel.
  • Icon ink --text-2 on --bg and --panel holds 3:1 in both themes; --label is only for disabled.

Gaps

  • Deck: .ibtn radius uses the old --r-2; switch to --radius-button. No 44 size, no filled round variant, no busy.
  • App: no icon button; screens use Pressable with text. Build IconButton with 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 Tokens colours.