Button
Primary action — the site’s black button.
Beta · Actions · packages/css/src/components/button.css · React on Base UI: @pepperui/button
.pui-button · variant --secondary --ghost --danger · size --small --large · __icon · --block. States: :hover :focus-visible :active :disabled.
Sizes across, states down.
What it is for
Section titled “What it is for”- Optional leading and trailing icons, off by default
- Three sizes: small, medium, large — medium is the site’s real one
- Five states: default, hover, focus, pressed, disabled
- Secondary and ghost are separate component sets
- Beside inputs or selects — the end of a filter row, a form’s inline action — the button is THIS one, the black default: an outlined secondary next to outlined controls reads as one more control
Use when the one action a view exists for — submit, save, create.
Do not use when two actions compete (one primary per view, principle 1); for navigation (that is a link); for on/off (that is Toggle).
Relates to Button - secondary, Button - ghost, Button - danger, Icon button, Button group, Icon.
Anti-patterns two primary buttons side by side · a primary button for cancel · ALL CAPS labels · a pill-shaped button (labels are pills, actions are squares) · a fill changed on the instance.
Keywords: action, primary, submit, cta, button
Figma — page Button: Button (15 variants) · Button - secondary (15 variants) · Button - ghost (15 variants) · Button - danger (15 variants)
shadcn: button
The markup — copy this
Section titled “The markup — copy this”The class, the parts, and nothing that is not in the package. The same block opens button.css.
<button class="pui-button">Save</button><button class="pui-button pui-button--secondary pui-button--small"> <span class="pui-icon pui-button__icon">…lucide svg…</span>Back</button><a class="pui-button pui-button--ghost" href="…">Skip</a>The modifiers
Section titled “The modifiers”| What it is | Note | |
|---|---|---|
| Variant | (none) primary · --secondary · --ghost · --danger | the four Figma sets; the registry’s variant |
| Size | --small 28 · (none) medium 36 · --large 44 | size/control/*; font 12 / 14 / 16 |
| State | :hover · :focus-visible · :active · :disabled, [aria-disabled=“true”] | — |
| Parts | __icon — a 16 px glyph before or after the label (Has icon leading / trailing) | — |
| Extra | --block — width 100 % | — |
Rule — beside inputs or selects (the end of a filter row, a form’s inline action) the button is the black one, never the outlined secondary — an outline next to outlined controls reads as one more control.
Accessibility — measured, not assumed
Section titled “Accessibility — measured, not assumed”Every number here is measured on the rendered component: contrast (npm run contrast:render), the focus ring (npm run focus) and the keyboard (npm run keyboard). The floors are principle 12’s.
Contrast
Section titled “Contrast”| ink on ground | reads | floor | the matrix calls it | |
|---|---|---|---|---|
#ffffff on #e42521 |
4.57 | 4.5 | Danger button label (promised) | ok |
#544d4d on #ffffff |
8.24 | 4.5 | text.muted on surface.page (promised) | ok |
#0b0202 on #f2f2f7 |
18.38 | 4.5 | text.default on surface.card (promised) | ok |
#0b0202 on #ffffff |
20.51 | 4.5 | text.default on surface.page (promised) | ok |
#ffffff on #0b0202 |
20.51 | 4.5 | Primary button label (promised) | ok |
5 distinct pair(s) rendered, 0 that axe calls a violation.
The focus ring
Section titled “The focus ring”| primitive | on | the indicator | over | the change | |
|---|---|---|---|---|---|
| Button | surface/page | shadow #6a66e4 2 px |
#ffffff |
4.53 | ok |
| Button | surface/card | shadow #6a66e4 2 px |
#f2f2f7 |
4.06 | ok |
| Button | surface/sunken | shadow #6a66e4 2 px |
#f2f2f2 |
4.05 | ok |
| Button - secondary | surface/page | shadow #6a66e4 2 px |
#ffffff |
4.53 | ok |
| Button - secondary | surface/card | shadow #6a66e4 2 px |
#f2f2f7 |
4.06 | ok |
| Button - secondary | surface/sunken | shadow #6a66e4 2 px |
#f2f2f2 |
4.05 | ok |
| Button - ghost | surface/page | shadow #6a66e4 2 px |
#ffffff |
4.53 | ok |
| Button - ghost | surface/card | shadow #6a66e4 2 px |
#f2f2f7 |
4.06 | ok |
| Button - ghost | surface/sunken | shadow #6a66e4 2 px |
#f2f2f2 |
4.05 | ok |
| Icon button | surface/page | shadow #6a66e4 2 px |
#ffffff |
4.53 | ok |
| Icon button | surface/card | shadow #6a66e4 2 px |
#f2f2f7 |
4.06 | ok |
| Icon button | surface/sunken | shadow #6a66e4 2 px |
#f2f2f2 |
4.05 | ok |
20 of 20 focus stop(s) in this block meet WCAG 2.4.13 on screen — the same crop screenshotted focused and unfocused, and the difference measured.
Keyboard
Section titled “Keyboard”Nothing in this component claims an ARIA role with a keyboard contract; its controls are ordinary ones and the browser already gives them their keys.
Tab order across the whole library is document order over 344 stops, and Shift+Tab is its exact reverse — no keyboard trap anywhere (WCAG 2.1.2).
Pepper UI v1.5.0
The Mobupps design system. 386 tokens, 88 components, 65 pages in the workshop — every page of this site generated from the repository, so nothing here is typed twice.