Core · Actions
Button
Button runs an action with a text label: Play, Follow, Remind me, Done.
- Use it for
- An action with a word on it: the one main action of a view, the action beside it, and the confirmation in a dialog that deletes something.
- Not for
- An action inside a sentence or a row of textTextLink
- An action that one glyph says better than a wordIconButton
- Narrowing what a feed showsChip
- Switching between views of the same thingSegmentedControl
- A choice whose options each need a picture or a line of explanationOptionTiles
Guidelines
- primary
- The one main action per view (Play, Follow creator, Done). Never two primaries side by side.
- secondary
- The supporting action beside the primary (Follow, More info).
- outline
- A standalone action that should not compete with the primary (Remind me).
- ghost
- Quiet actions in rows and headers (See all).
- destructive
- The one action that confirms a deletion or takes access away (Delete my account, Disconnect Claude), filled in vermilion (
danger). It lives in the dialog that asks first, and it is that dialog's main action, in place of a primary. The way into it is aTextLink variant="destructive", never a red button on the page. Removals you can simply undo (Remove a tag, Unfollow) stay ghost.
size="lg"for hero actions,mdby default,smfor desktop-only secondary actions. Every size exceptsmmeets the 44px target.shape="pill"on phones; the default rounded corner (radius-md) on desktop.- Labels say what happens, in sentence case. Add
aria-labelwhen the visible label needs its object ("Follow" → "Follow EBA"). - Pass
hrefto render a link that looks like a button. - An action that spends fragments passes
cost("~100"): the price sits after the label behind a hairline, with the fragment mark. Keep the label a plain verb ("Generate", "Start", "Retake"); never write the price into the label. - media and media-secondary: the same two roles drawn over video or key art (hero Play and More info, Skip intro, shorts CTA). They never change with the theme.
- Pressing sinks it to 97% on
motion-instant; it settles back onmotion-fast. - A disabled primary turns into a quiet fill (
button-disabled-bg), not a faded white. Say why it's disabled in a visible line near it, never only in a tooltip.
Props
variant'primary' | 'secondary' | 'outline' | 'ghost' | 'media' | 'media-secondary' | 'destructive'size'sm' | 'md' | 'lg'shape'rounded' | 'pill'iconIconNameiconEndIconNamefullWidthbooleanhrefstringdisabledbooleancoststringonClickMouseEventHandlerchildrenReactNodeclassNamestring'aria-label'stringTypeScript
export interface ButtonProps { /** destructive: the one action that confirms a deletion or takes access away, in the dialog that asks for it. */ variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'media' | 'media-secondary' | 'destructive'; size?: 'sm' | 'md' | 'lg'; /** pill on phones; rounded (radius-md) on desktop. */ shape?: 'rounded' | 'pill'; icon?: IconName; iconEnd?: IconName; fullWidth?: boolean; href?: string; disabled?: boolean; /** What the action is expected to cost in fragments, e.g. "~100": shown after the label with the fragment mark. */ cost?: string; onClick?: MouseEventHandler; children?: ReactNode; className?: string; 'aria-label'?: string; }
| Token | Light | Dark | Use |
|---|---|---|---|
button-primary-bg | action-primary | action-primary | Button primary: fill. |
button-primary-fg | text-inverse | text-inverse | Button primary: label and icon. |
button-secondary-bg | action-secondary | action-secondary | Button secondary: fill. |
button-secondary-fg | text-primary | text-primary | Button secondary: label and icon. |
button-outline-border | border-control | border-control | Button outline: edge. |
button-outline-fg | text-primary | text-primary | Button outline: label and icon. |
button-ghost-fg | text-secondary | text-secondary | Button ghost: label and icon. |
button-ghost-hover-bg | bg-fill-subtle | bg-fill-subtle | Button ghost: hover fill. |
button-disabled-bg | bg-fill-subtle | bg-fill-subtle | Button primary or media while it can't be used yet: a quiet fill, never a faded white. |
button-disabled-fg | text-muted | text-muted | Button label while disabled. |
button-media-bg | action-media | action-media | Button media: fill over video or artwork. |
button-media-fg | text-on-action-media | text-on-action-media | Button media: label and icon. |
button-media-secondary-bg | action-media-secondary | action-media-secondary | Button media-secondary: glass fill over video or artwork. |
button-media-secondary-fg | text-on-media | text-on-media | Button media-secondary: label and icon. |
button-media-secondary-border | border-glass-media | border-glass-media | Button media-secondary: edge. |
button-destructive-bg | danger | danger | Button destructive: the fill of the action that confirms a deletion or takes access away. |
button-destructive-fg | on-danger | on-danger | Button destructive: its label and icon. |