Skip to content
skrewww

Actions

Button

Button is a primary interactive trigger for user actions — submit, confirm, navigate, or initiate a process.

Style (Primary/Secondary/Danger) × Size (Small/Medium/Large) × State (Default/Hover/Pressed/Focused/Disabled) — 45 variants

stableReact AvailableFigma AvailableDocs Partial
React last updated
2026-07-11
Documentation last updated
2026-06-01
Accessibility target
WCAG 2.2 AA (target)
Version
1.0.0

Known open questions

  • Confirm exact control height and horizontal padding per size from Figma.
  • Trailing icon is not defined as a separate Figma property on Button — supported in code for composition; confirm with design.
  • UNRESOLVED DESIGN REVIEW: Primary Glass Hover dark content can appear visually muddy over high-frequency/complex backgrounds. Current #17181B content and 24% hover fill match live Figma; any readability adjustment must be approved in Figma first.

Purpose

Primary interactive trigger for user actions — submit, confirm, navigate, initiate a process.

Anatomy

Button = native button or link + visual surface + content row (optional leading/trailing icon + label). Icons are consumer-supplied nodes; Button does not wrap them in SVG or change their paths.

Variants and states

primary · secondary · danger

When to use

For the main action(s) in a view. Use Primary for the single most important action, Secondary for alternative/less prominent actions, Danger for destructive actions requiring confirmation. A prominent navigational CTA may use Button with href, which renders with link semantics.

When not to use

For inline or lower-emphasis page-to-page navigation (use Link). For toggling a binary state (use Switch). For a single icon-only action (use Icon Button).

Accessibility

The action variant renders a native <button>; the href variant renders a semantic link. Keyboard: Tab to focus, Enter/Space to activate buttons, Enter to activate links. Focus uses semantic/focus-ring (5.31:1 contrast, confirmed on the Accessibility page). Never rely on the Disabled state alone to explain unavailability — pair with visible explanatory text nearby.

Common mistakes

More than one Primary button in the same view (dilutes hierarchy). Not disabling the button during an async action (allows double-submission). Skipping a confirmation step for Danger actions just because the button is red.

Properties

Style × Size × State as variants. Label (text). Has Icon (boolean) + Icon (instance-swap). React icons inherit Button's CSS color via SVG currentColor — Primary/Danger follow surface-content (light on Flat/Gradient, dark on Glass); Secondary follows primary text. Button does not recolor or rewrite icon artwork.

How do Button icons get their color?

Button sets CSS color only. Primary and Danger use surface-content semantics: inverse/light on Flat and Gradient, dark on Glass. Secondary uses primary text color on every surface. Compatible icons inherit that color through SVG currentColor — Phosphor defaults fill to currentColor; stroke icons should use stroke=currentColor. Button does not set fill, stroke, or icon-specific paint, and it does not rewrite icon artwork. Any compatible icon inherits the same foreground automatically. Icon-only actions use this same Button with an aria-label; there is no separate Icon Button implementation.

What is the difference between Button and Link?

Use Button for actions such as submitting, confirming, or starting a process. A prominent navigational CTA may use Button with href, which renders with link semantics. Use Link for inline or lower-emphasis navigation where the control should read as part of the surrounding content.

Which Button variant should I use?

Use Primary for the single most important action in a view, Secondary for alternative or less-prominent actions, and Danger for destructive actions that require clear confirmation.

Tokens used

component/radius/controlsemantic/action/primarysemantic/action/dangersemantic/surface/defaultsemantic/text/inversesemantic/text/primarysemantic/focus-ringopacity/disabled

Component API

PropTypeDefaultDescription
variant"primary" | "secondary" | "danger""primary"Visual style mapped to semantic action tokens.
size"sm" | "md" | "lg""md"Control height and typography scale.
loadingbooleanfalseDisables interaction, sets aria-busy, and shows a spinner with screen-reader text.
fullWidthbooleanfalseStretches the control to the width of its container.
leadingIconReactNode—Optional icon before the label (Figma: Has Icon + Icon).
trailingIconReactNode—Optional icon after the label. Not confirmed as a Figma Button property.
hrefstring—When set, renders as a semantic link (Next.js Link or native anchor for external URLs).
targetstring—Link target. `_blank` automatically adds `rel="noopener noreferrer"` when rel is omitted.
disabledbooleanfalseNative disabled for buttons; aria-disabled non-navigating link for href variant.
aria-labelstring—Required accessible name for icon-only buttons.

React example

Copy React example
import { Plus } from "@phosphor-icons/react";
import { Button } from "@/components/ui/Button";

export function Example() {
  return (
    <Button variant="primary" leadingIcon={<Plus size={16} />} type="button">
      Create project
    </Button>
  );
}