Skip to content
ArcadiaDesign

TSX

Variants only reassign --button-* knobs, so every variant shares one geometry. Use primary for the one action that moves the flow forward and ghost for everything around it. Primary is ink, never the accent: the beacon is kept for lights. Every button is a pill at weight 500 (shape="rounded" keeps the size's radius); its states change colour only, over 150ms. A leading icon, a trailing iconEnd and a quiet hint key sit inside the same padding.

Examples

Variants

Six variants on one geometry. danger is for the destructive action itself, not for the button that opens a confirmation.

Tones

plan is Plan mode's distance blue.

On primary it fills the button; on the other variants it tints the label. An answer to the agent (Submit, Allow) is the ordinary ink primary: amber stays on literal lights, never a control.

States

loading puts the spinner in the icon's slot (or over a label-only button), so the width never changes and focus stays.

pressed draws the toggled fill and sets aria-pressed. focusableWhenDisabled keeps a disabled button focusable, so a tooltip can say why. progress (0..1) fills a ring in the icon's slot while the label says the number.

Press

A pressed button sets down: it scales to --press-scale (2%) in --duration-instant and springs back on --ease-release, while its fill holds the step it took under the pointer.

Only the scale moves, never the layout. Hold one down and let go.

A label that gives way

Say a label in fewer words first.

Where a button must hold a long one, such as a model's name, pass a FadeText with shorter forms as its label: the longest that fits shows, the last fades, and the button is still named by the whole label. Narrow the room to see it.

Use render with nativeButton={false} to keep normal link semantics.

Variant and state
Size and kind

rootStyle takes where the button sits (margins, self-alignment, flex and grid placement, size bounds) and the knobs below, each a token of its kind. A colour, a raw length or any other property is a type error: a look that comes up twice becomes a variant.

TSX
--button-h
Ref<Control>Default var(--control-md)
Height.
--button-px
Ref<Space>Default var(--space-2-5)
Inline padding (a pill adds --space-1).
--button-radius
Ref<Radius>Default var(--radius-lg)
Corner radius (a pill is full).
--button-gap
Ref<Space>Default var(--space-1-5)
Gap between icon, label and hint.
--button-icon
Ref<IconSize>Default var(--icon-text)
The icon's frame, and the spinner's and the progress ring's: --icon-text beside the label (the whole-pixel frame of a drawing as tall as the label's capitals, stroked at the medium stem); an icon-only button takes the standalone scale.
--button-bg
PaintDefault transparent
Fill at rest.
--button-bg-hover
PaintDefault var(--fill-4)
Fill under the pointer, or while its popup is open.
--button-bg-active
PaintDefault var(--button-bg-hover)
Fill while pressed.
--button-bg-pressed
PaintDefault var(--fill-3)
Fill when toggled on (pressed).
--button-bg-disabled
PaintDefault transparent
Fill when disabled.
--button-fg
PaintDefault var(--text-2)
Label and icon colour at rest.
--button-fg-hover
PaintDefault var(--text-1)
Label colour under the pointer.
--button-fg-pressed
PaintDefault var(--text-1)
Label colour when toggled on.
--button-fg-disabled
PaintDefault var(--text-4)
Label colour when disabled.
--button-border
PaintDefault transparent
Border colour (1px) at rest.
--button-border-hover
PaintDefault var(--button-border)
Border colour under the pointer.
--button-hint
PaintDefault var(--text-3)
Key hint ink.
--button-hint-disabled
PaintDefault var(--button-fg-disabled)
Key hint ink when disabled.

Variants, sizes and tones only reassign knobs, and each state reads its own (--button-bg-hover, --button-hint-disabled), so a knob you set never drops a state.

A native <button> by default. loading sets aria-busy and blocks activation without dropping focus; pressed sets aria-pressed. Keyboard focus draws the 2px ring, outset by 2px. A FadeText label names the button by its whole text, whichever form shows.

A hint is drawn only: it is aria-hidden and becomes aria-keyshortcuts ("⇧Tab" → Shift+Tab, "⏎" → Enter), so the accessible name is the label alone. Binding the key is yours.

Enter
Activates the button
Space
Activates the button

Button

variant
"primary" | "secondary" | "outline" | "ghost" | "text" | "danger"Default "ghost"
primary is the monochrome ink CTA, for the one action that moves the flow forward (send).
size
"xs" | "sm" | "md" | "lg"Default "md"
Heights 20 / 24 / 28 / 32.
shape
"rounded" | "pill"Default "pill"
tone
"neutral" | "plan"
plan is Plan mode's distance blue: a blue CTA on primary, blue text elsewhere. There is no amber tone: the sodium light is for literal lights (a running edge, a needs-you dot), never a control's fill.
icon
IconName
Leading icon. While loading, a spinner takes its slot so the width never changes.
iconEnd
IconName
hint
ShortcutSpec | ReactElement
Trailing key hint, drawn quietly: "⇧Tab", "⏎", ["⌘","K"], "mod+k", or a KbdGroup. Visual only (aria-hidden, so the name stays "Allow", not "Allow ⏎"); the keys are exposed as aria-keyshortcuts on the button. Binding the key is yours.
loading
booleanDefault false
Busy: aria-busy, a spinner, and activation blocked while focus is kept (no native disabled).
progress
number
Determinate progress, 0..1: a ring fills in the icon's slot (a download, an upload) while the label says the number. The button stays pressable; loading wins when both are set.
pressed
boolean
Toggle state: sets aria-pressed and data-pressed. Omit for a plain action button.
labelRow
boolean
The label is a row of parts (an avatar beside words), each on its capitals.
rootStyle
ButtonStyle
Typed StyleX override: where the button sits (margins, self-alignment, flex and grid placement, size bounds) and its --button-* knobs.
render
RenderProp<{ disabled: boolean }>
Replace the rendered element, e.g. render={<a href="/settings" />}, or a function of props and state.
disabled
boolean
Native disabled: no pointer events, no tooltip, skipped by Tab.
focusableWhenDisabled
boolean
Keep a disabled control focusable and hoverable (aria-disabled), so a tooltip can say why.
nativeButton
boolean
Button semantics, inferred from what renders: a <button> is native, an <a> stays a link, anything else gets role="button" and key handling. false forces button semantics on whatever renders (a link acting as a button).
className
string
A product's hook class for its own layout rules. Being retired: new code places a button with rootStyle; the call sites left are counted by ui.lint (kit-classname) and only go down.

Rest props go to <button> props.