You're in the Library

Button

Triggers an action on the current screen: save, publish, approve, open a dialog. If it takes the user somewhere else, it is a Link, not a Button.
Stable
Edit

Preview appears after the next Storybook update.

Button example
<Button>Publish tokens</Button>
Rendered with the real componentFrom the code
Light
Dark

When to use

Use it for

  • The user commits an action on this screen: Publish, Approve, Create token, Open pull request.
  • Primary: the one action you most want the user to take in this view. One per form, dialog or page section.
  • Secondary: the alternatives next to the primary, like Save as draft or Request changes.
  • Ghost: low-emphasis actions repeated in lists, tables or toolbars (Edit, View diff), where filled buttons would fight for attention.
  • Danger: destructive actions (Delete, Revoke access). Usually inside a confirmation dialog.
  • Pressed: a toggle that stays on, like Compare or Show what changed in a toolbar. Set pressed to true or false; the selected look and aria-pressed follow it.
  • Link: an action on this screen inside a sentence, like "Show only these" after a count. It looks like a link and stays a button.
  • Inverse: the action on a strong surface (Deep Harbor, the inverse terminal bars), where secondary would disappear.
  • Size sm in dense rows and toolbars, md everywhere else, lg only for a single call to action on an empty or onboarding screen. Heights follow the density setting.

Not for

  • Going to another page or URL: use Link. Links and buttons are announced differently and users expect Cmd+click to open links in a new tab.
  • Navigation with variant link: a link-looking button still does not go anywhere. Use Link to go to another page.
  • Turning a setting on or off in a form: use Switch or Checkbox. A pressed Button is for toolbar toggles that change the view.
  • Picking one of several views: use Tabs or an attached ButtonGroup.
  • More than three actions in one row: keep the main ones and move the rest to a Menu behind an IconButton.
  • To show a status that cannot be clicked: use a Badge.

Do and don't

Every example is rendered with the real component, so it always matches what ships.

Do

Start the label with a verb that names the outcome

"Publish tokens" and "Approve change" tell the user what happens before they click.

Do

One primary per view, the others step down

Hierarchy tells users where to look first. Secondary and ghost carry the alternatives.

Do

Show progress with loading, keep the label

The button stays in place and focusable, so keyboard users do not lose their position. Change the label to the present participle.

Do

A toggle says its state with pressed

Pass pressed true or false, never swap variants to show the state. The look, aria-pressed and a script driving a static page stay in step.

Don't

Two primary buttons side by side

When everything is primary, nothing is. Users hesitate and pick at random.

Don't

Vague labels

"OK" and "Click here" say nothing out of context, and screen reader users often list buttons out of context.

Don't

Danger for actions that are not destructive

Red trains people to ignore red. Keep danger for actions that remove or revoke something.

Accessibility

  • Native button through Base UI: Enter and Space activate it, it is in the tab order, no extra ARIA needed.
  • The focus ring (focus.ring.color, 2px, offset 2px) shows on keyboard focus only, never on mouse click.
  • Loading sets aria-busy and aria-disabled, ignores clicks, and keeps the button focusable so keyboard users keep their place.
  • Disabled removes the button from the tab order and explains nothing. Prefer an enabled button plus a message saying what is missing.
  • pressed sets aria-pressed, so screen readers announce a toggle button and its state. Leave it unset on plain buttons: aria-pressed false still announces a toggle.
  • Variant link keeps the native button: Enter and Space work, it is announced as a button, and the focus ring is the DS ring.
  • With render={<a href />} the element stays a link (role link), which is right for navigation styled as a button.
  • Heights are 32/40/48px comfortable and 28/32/40px compact: all above the WCAG 2.2 minimum target of 24px.

Content guidelines

  • Sentence case, no trailing period: "Open pull request", not "Open Pull Request."
  • Verb plus object, two to four words. Drop the object only when context makes it obvious (Save, Cancel).
  • The same verb follows through the flow: Publish, Publishing, Published.
  • Name the count on bulk actions: "Delete 3 tokens".
  • Never "OK", "Submit", "Yes" or "Click here".

From the code Generated from the component, not editable here.

Anatomy and tokens

Read from packages/react/src/components/Button/Button.module.css at build time. Every value is a token: open one to see its alias chain in both themes.

52 tokens36 semantic or component, change with theme and brand7 density, change with comfortable or compact9 from the shared scales

Motion and interaction

Button animates on hover and press. Try it in the preview at the top.

WhenWhat changesDurationEasing
HoverBackground and text colormotion.duration.instant75 msmotion.easing.standardsettles in place
PressBackgroundmotion.duration.instant75 msmotion.easing.standardsettles in place

With reduced motion turned on, every change happens at once.

From the code Generated from the component, not editable here.

Props

Parsed from the TypeScript types in packages/react/src/components/Button/Button.tsx, so this table cannot drift from the code. It also accepts every native <button> attribute.

PropTypeDefaultDescription
variant'primary' | 'secondary' | 'ghost' | 'danger' | 'link' | 'inverse''primary'Visual emphasis. Use one primary action per view; danger only for destructive actions.
size'sm' | 'md' | 'lg''md'Height follows the density tokens: sm 32/28, md 40/32, lg 48/40 (comfortable/compact).
loadingbooleanfalseShows a spinner, keeps the button focusable, ignores clicks and sets aria-busy.
iconStartLucideIconnoneLucide icon before the label. Replaced by the spinner while loading.
iconEndLucideIconnoneLucide icon after the label, for example a chevron on a menu trigger.
fullWidthbooleanfalseStretch to the width of the container.
pressedbooleannoneMakes the button a toggle: sets `aria-pressed` and shows the selected look when true. Leave unset for a plain button. A script can flip `aria-pressed` on a statically rendered toggle: the look follows the attribute.
renderReactElementnoneRender another element with button styling, for example a router link: `render={<a href="/tokens" />}`. The element keeps its own role (a link stays a link), so only use it for navigation.

Playground

Change the props and see the real Button update. Controls are generated from the TypeScript props; nothing here changes the system.

Code for these props
<Button>Publish tokens</Button>

size

Workbench

For contributors: every state with the accessibility panel, in the component workbench (Storybook). The stories double as visual test fixtures; the guidance on this page is the user documentation (open Button in Storybook).

Embed Storybook stories here (12)

Loads from https://bridge-storybook.floriangouloubi.com (set STORYBOOK_URL at build time). If Storybook is not running, the frames stay empty.

Primary

Secondary

Ghost

Danger

WithIcons

Loading

Disabled

Link

Inverse

Toggle

FullWidth

AllStates

From the code Generated from the component, not editable here.

Changelog

  • New

    @bridge-ds/react/dom gains the Dialog for pages without React at runtime: dialogHtml() renders the exact markup of <DialogContent> (same classes, same look), and bindDialogs(), openDialog(), closeDialog(), isDialogOpen() and dialogReturnValue() give it the React Dialog behavior: focus moves in and is trapped, the page behind is inert and does not scroll, Escape and the scrim close it, focus returns to the trigger, aria-modal, open, cancel and close events, and a <form method="dialog"> inside closes it with the submit button's value

    v1.0.0, Nov 11, 2026

  • New

    Actions: Button gains danger variant, lg size, iconStart, iconEnd, fullWidth and render (a link stays a link); loading keeps focus

    v1.0.0, Nov 11, 2026

  • New

    Initial foundation: DTCG 2025.10 primitives and light/dark semantic tokens built to CSS variables, plus Button, Input, Badge and Card consuming tokens only

    v1.0.0, Nov 11, 2026

  • New

    SegmentedControl: pick one of two to five views or modes in a row of attached buttons (Base UI RadioGroup with native button radios)

    v1.0.0, Nov 11, 2026

  • New

    Studio reads as one app: compact page headers with no hero or lede, GitHub status only in the global header (connect button, or your login with repository, access and disconnect), a Drafts counter in the header that opens the draft as a side sheet, and the token explorer full bleed with one toolbar row (search, Filters, Modes, View, New token) and compact 40px rows

    v1.0.0, Nov 11, 2026

  • Fix

    Measure args reach the story: when Storybook drops URL args it cannot validate (a story that declares no args, like Carbon's Button), the measure sends them the way the Controls panel does and waits for the re-render

    v1.0.0, Nov 11, 2026

  • Fix

    The values referee of the Figma fix loop finds a variant when the fidelity run names it with fewer axes than the rebuilt set has (the run keeps only the axes that vary between its shots, the Button set also has Loading and Full width)

    v1.0.0, Nov 11, 2026

  • Fix

    Measured only: a frame bigger than its children plus its padding on an axis its parent does not size keeps its measured size (a 48 px button around a 16 px icon, a 24 px label row around a 16 px label), read from the boxes when the measure did not mark it as sized

    v1.0.0, Nov 11, 2026

  • Fix

    Component specs treat a button or a select (including the Base UI part that renders one, read from Base UI's docs) as border-box: NumberField steppers are their height token wide again

    v1.0.0, Nov 11, 2026

  • Fix

    withRepoTokenPaths binds a measured field whose declaration is a variable alone (var(--cds-button-primary, #0f62fe)) to the written token of its chain, so a design system measured without token files gets its colors and type bound to variables

    v1.0.0, Nov 11, 2026

  • Fix

    bridge audit no longer counts a native element passed as a component's render element (<Button render={<a href="/tokens" />}>) as a separate native UI instance, as it already did for router links: the DS component is the UI

    v1.0.0, Nov 11, 2026

  • Fix

    Usage guidance for Button

    v1.0.0, Nov 11, 2026

  • Fix

    Button stories take disabled as a control, so the Storybook measure reaches the Disabled variants

    v1.0.0, Nov 11, 2026

  • Fix

    Add usage guidance files for Button, Input, Badge and Card, plus the list of color pairs to check for contrast

    v1.0.0, Nov 11, 2026

  • Fix

    Adds the generated component-specs.json (run npm run specs) for Button, IconButton, Badge, Tag, Input and Card, read by the DS Bridge Figma plugin to pull them as component sets

    v1.0.0, Nov 11, 2026

  • Fix

    Exploration page, "What this changes in the library": each piece is one header line (kind tile, name, one tag, origin), a new component shows the piece as built with one "Add Timeline to the library" tick, and a change to a component part reads like the component's own options ("Padding: Comfortable", 20 px, +4 px) with the new values to tick, the ones already in the library under them, Rename, and a button that names the outcome

    v1.0.0, Nov 11, 2026

  • Fix

    Studio: the account menu and other dropdown menus no longer make their button jump when they open

    v1.0.0, Nov 11, 2026

  • Fix

    Phones: the "..." More actions button stays at the end of the title's line instead of dropping onto a line of its own

    v1.0.0, Nov 11, 2026

  • Fix

    An exploration's button reads Send to Figma, then Send to Figma again, instead of Put in Figma

    v1.0.0, Nov 11, 2026

  • Fix

    Studio setup: every step's main button does the step (Send for review, I installed it, Create the team); a done step says where it goes next; Do this later is the only skip

    v1.0.0, Nov 11, 2026

  • Fix

    Exploration page: one "Open the preview" button (it opens the preview with the Bridge toolbar, Before and After, when the product supports it) beside "Continue in Claude", as one row on desktop and stacked on phones

    v1.0.0, Nov 11, 2026

  • Fix

    Homepage closing: the profile card the walkers carry is now a DS Card with a DS Avatar and a full-width "Connect on LinkedIn" button

    v1.0.0, Nov 11, 2026

  • Fix

    Studio home: the greeting drawing keeps a gap above the task list instead of touching it, and the row button sits at the far right (the DS Button margin reset was overriding it)

    v1.0.0, Nov 11, 2026

  • Fix

    Studio home: the status pill on a Needs you row sits right after the row's text; the button keeps the far edge

    v1.0.0, Nov 11, 2026

  • Fix

    Studio home: Needs you rows get a verb button by count

    v1.0.0, Nov 11, 2026

  • Fix

    Library page headers: "Edit in Studio" becomes a quiet ghost button in the top right corner of the header zone instead of a button next to the title

    v1.0.0, Nov 11, 2026

  • Fix

    Studio Overview greeting is now your to-do list: one row per change or component request that waits for you, with why in plain words and the button for the task on the same row

    v1.0.0, Nov 11, 2026

  • Fix

    Check fidelity: "Fix with Claude" becomes one button, "Ask Claude to fix it"

    v1.0.0, Nov 11, 2026

  • Fix

    One verb for the push flow: "Push your edits for review", step "Push for review", button "Push for review"

    v1.0.0, Nov 11, 2026

All release notes