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
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "primary" | "secondary" | "danger" | "primary" | Visual style mapped to semantic action tokens. |
| size | "sm" | "md" | "lg" | "md" | Control height and typography scale. |
| loading | boolean | false | Disables interaction, sets aria-busy, and shows a spinner with screen-reader text. |
| fullWidth | boolean | false | Stretches the control to the width of its container. |
| leadingIcon | ReactNode | — | Optional icon before the label (Figma: Has Icon + Icon). |
| trailingIcon | ReactNode | — | Optional icon after the label. Not confirmed as a Figma Button property. |
| href | string | — | When set, renders as a semantic link (Next.js Link or native anchor for external URLs). |
| target | string | — | Link target. `_blank` automatically adds `rel="noopener noreferrer"` when rel is omitted. |
| disabled | boolean | false | Native disabled for buttons; aria-disabled non-navigating link for href variant. |
| aria-label | string | — | 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>
);
}