Persona

Button

Actions · 16 props · Updated Sep 22, 2026

Buttons perform actions. One component covers both standard buttons and icon-only buttons, and six variants set how much emphasis a button carries.

  • Container the clickable shape that houses icon and/or label.
  • Label (optional) communicates the action (standard buttons only).
  • Leading icon (optional) appears before the label.
  • Trailing icon (optional) appears after the label.

Use a button when activating it changes something: submitting a form, creating a record, opening or closing a panel, running a command, or acting on an object in a row or toolbar.

Use a Link when activating it takes the reader somewhere and changes nothing. For an on/off state use a Toggle; for filtering and selection use a Sticker; when the action is one of several related choices, use a Dropdown Menu.

A button that navigates still announces itself as a button

Section titled “A button that navigates still announces itself as a button”

to changes what Button renders. A relative path renders a react-router-dom link; an absolute https:// URL renders an anchor that opens in a new tab, and target overrides that.

So the button-or-link decision is about how the destination should look and announce itself, not about which component can navigate. A button that navigates still reads as a button to a screen reader, which is why to suits actions that happen to have a URL, like “Open dashboard”, rather than links inside prose.

  • primary The page's main action. Use one primary action per view or section.
  • basic General actions with regular emphasis.
  • outline Secondary actions that need visual affordance without heavy emphasis.
  • text Lowest emphasis; use when contextual, inline, or when visual noise must be minimal.
  • inverse For dark or colored surfaces where other variants would not meet contrast or feel too heavy.
  • danger For destructive or high-risk actions (delete, remove, reset). Always confirm or provide undo.
3 of 6 variants fall below the 4.5:1 WCAG AA threshold for normal text: 2 in the dark theme only (△), 1 in both themes (▲).

These six are in emphasis order, loudest first: primary, basic, outline, text, inverse, danger. The props table lists them in the order the type declares them, which carries no meaning.

Use inverse on a dark or colored surface, and onSecondaryBackground when a variant needs adjusting for a secondary background it was not drawn against.

A primary marks the single most important action available. Two of them in one view cancel each other out, because emphasis is relative: the reader cannot tell which one the screen is asking for. Pair a primary with outline or basic for the actions beside it, and drop to text inline or in dense areas.

Placement carries emphasis too. A primary in a position the eye reaches last does not read as the main action however it is styled.

Reserve the danger variant for what it destroys

Section titled “Reserve the danger variant for what it destroys”

danger is red fill, and red spends attention the rest of the page then cannot use. Keep it for outcomes that actually destroy something, and pair it with text naming the consequence.

Let an async handler own the pending state

Section titled “Let an async handler own the pending state”

isPending shows a spinner and disables the button, and it also fires on its own when onClick returns a promise, clearing once that promise settles. An async handler therefore gets pending state without being asked, so setting isPending by hand as well can leave the button disabled after the promise resolves.

Set the button type explicitly inside a form

Section titled “Set the button type explicitly inside a form”

type’s own description records why: many existing call sites depend on a button defaulting to submit, so the default was never changed to button. Inside a form, a button meant only to open a panel will submit that form unless you set type="button".

A standard button takes its accessible name from its visible text. An icon button has no text, so without aria-label it reaches a screen reader as an unnamed button. Name it for the action rather than the glyph: aria-label="Close", not aria-label="X icon". Pair it with width="icon", which sizes the button square to its own height.

Activation works with both Enter and Space. When you disable a button, prefer aria-disabled over removing it from the tab order, so the reason for the disabled state can still be reached.

Start the label with a verb, and name the object

Section titled “Start the label with a verb, and name the object”

A button label is the action it performs: “Create invoice”, “Download CSV”. Buttons are read out of context, in a toolbar or a row of icons or a screen reader’s list of elements, so a label that leans on the text beside it has no meaning where it is needed most.

A danger button names the thing it destroys. “Delete file” is recoverable knowledge at the moment of clicking; “Remove” is not.

Capitalization, punctuation and the rules that apply to every label are on Writing.

NameType
variant

basic, primary, inverse, danger, text, outline

Visual style of the button.

  • primary – filled brand colour, for the primary action on a surface.
  • basic – subtle grey fill, for secondary actions.
  • outline – transparent with a border, alternative secondary style.
  • text – no background or border, for tertiary / inline actions.
  • inverse – inverted fill for use on dark/coloured backgrounds.
  • danger – red fill, for destructive actions.
isPending

boolean

Shows a loading spinner and disables the button. Also triggers automatically when onClick returns a Promise, clearing once the promise settles.

prefixIcon

ReactNode

Icon rendered to the left of the button label. Import from @persona/icons.

prefixIconType

basic, inherit

Controls how the prefix icon inherits sizing.

  • basic – icon is rendered inside a fixed-size container.
  • inherit – icon stretches to fill the surrounding element’s dimensions.
suffixIcon

ReactNode

Icon rendered to the right of the button label. Import from @persona/icons.

size

md, sm

Height and typography scale of the button.

to

To

Changes the button into a link. Pass a relative path to use react-router-dom <Link>, or an absolute https:// URL to render an <a> tag that opens in a new tab.

target

HTMLAttributeAnchorTarget

Overrides the target of the link if to is provided and is an external link.

type

button, reset, submit

Note: we may want to default this to ‘button’ in the future, but many instances of this Button component in dashboard currently rely on the default ‘submit’ behavior that occurs when using this in redux-form so we’ll have to comb through and manually add the type=‘submit’ prop.

width

full, auto, button, icon

Controls the width of the button.

  • full – stretches to fill its container (width: 100%).
  • auto – shrinks to fit its content.
  • button – a fixed medium element width (from design tokens).
  • icon – square, sized to match the button height; use for icon-only buttons.
onClick

MaybePromise

onSecondaryBackground

boolean

In some cases we render a button on a secondary background, which needs an adjustment for certain variants.

hovered

boolean

Visual state override. Forces the :hover styling on permanently, whatever the real state is. For specimens and visual tests, not application code.

focused

boolean

Visual state override. Forces the :focus styling on permanently, whatever the real state is. For specimens and visual tests, not application code.

active

boolean

Visual state override. Forces the :focus-within styling on permanently, whatever the real state is. For specimens and visual tests, not application code.

mixed

boolean

Visual state override. Forces the :indeterminate styling on permanently, whatever the real state is. For specimens and visual tests, not application code.

Also accepts every prop of ComponentPropsWithoutRef<‘button’>.

Was this page helpful?