Components/Action
Button
Buttons are the controls a reader presses to make something happen. They advertise the actions a screen offers and carry them out on the spot, and their prominence is how a screen says which of those actions it recommends. A button’s label names the action, so the reader knows the outcome before committing to it.
import { Button } from '@growth-protocol-ai/alpha';When to use
Reach for a button when the reader is doing something rather than going somewhere: submitting, saving, deleting, opening a dialog. Give each action group one primary button and let the rest of the group take a lower emphasis, so a reader can see at a glance which action the screen is recommending.
When not to use
Do not use a button to move the reader to another page — that is a link, and a link is what gives them a middle-click, a new tab and a preview of the destination. A control that holds a value rather than firing once is not a button either; reach for Toggle, Checkbox or Dropdown.
variant4 values
Variant is what the action is worth, and what picks it is the weight the action deserves next to everything else on the screen rather than the colour you want. Danger is the exception — it is picked by what the action does. When the button sits in a form or a data-dense view, outlined is the safe answer.
| variant | When to use |
|---|---|
primary | The main action — the one thing you most want the reader to do. One per action group. |
secondary | A supporting action beside a primary one: Cancel, Skip, Back. Solid enough to notice, neutral enough not to compete. |
outlined | The safe default. It shares the neutral surface, border and height of an input, so it sits in a form without a visual mismatch. |
danger | An action that destroys something: delete, revoke, discard. It carries the same weight as a primary and reads as a warning, so a screen has at most one. |
emphasis3 values
Emphasis is how much room the action gets to say it with, and it runs independently of variant. Ghost is effectively tertiary whatever it is applied to — a primary ghost is quieter than a secondary solid flat.
| emphasis | When to use |
|---|---|
depth | Elevated. The most prominent of the three at any variant — for an action that needs weight in a busy or hero context. |
solidFlat | Solid colour, no elevation. The everyday default, and the right answer most of the time. |
ghost | Transparent at rest, filling on interaction. For the quietest action in a group: toolbar utilities, row triggers, inline edits. |
size2 values
Size is geometry only — it never changes what an action means. Both heights are shared with the dropdown trigger, so the two line up when placed side by side.
| size | When to use |
|---|---|
regular | 36px. Forms, inline actions and toolbars. |
large | 52px. Page-level calls to action and hero placements. |
rounding2 values
Two radii, and the choice is about register rather than emphasis. Keep it consistent within a screen: one pill among square buttons reads as a mistake.
| rounding | When to use |
|---|---|
default | 12px. Forms, dialogs and internal UI — the everyday default. |
full | A pill. Marketing-style and hero calls to action. |
| State | When to use |
|---|---|
Disabled | Swaps the fill for --button-inactive at solid flat and depth, drops the shadow from a depth button, and takes the label to --text-disabled at all three. A ghost stays transparent — its disabled rule is the more specific one — so what says it is off there is the label alone. It is a different button, not a faded one, and it is skipped by the keyboard. |
Pending | A spinner displaces the leading glyph rather than joining it, so no pair appears on that side. A trailing glyph is untouched and still renders, which is worth knowing before pairing loading with iconRight. aria-busy is set and the button is disabled, so the action cannot be fired twice. On an icon-only button the spinner replaces the glyph entirely. |
An icon-only button needs an aria-label. The glyph is not an accessible name, and a button announced as "button" tells a screen-reader user nothing.
The focus ring is driven by :focus-visible, so it appears for keyboard navigation and stays out of the way for pointer users. Do not suppress it through className.
A pending button carries aria-busy and is disabled, which is what stops assistive technology reporting it as actionable mid-flight.
Under asChild the rendered element is yours, so its semantics are too — an <a> is a link and will not answer the space bar the way a button does.
One primary button per action group. A screen with a modal over it has two groups, and each may have its own.
Treat every ghost as tertiary, whatever its variant. Reaching for a primary ghost to make a quiet action feel important defeats both.
Reach for danger only when the action destroys something the reader cannot get back. Red on everything that merely matters teaches people to click through it.
Keep one rounding per screen. The pill is a register, not an emphasis level, so mixing the two reads as an accident.
Icons and icon-only
A glyph on either side, both, or a square button that is nothing but the glyph. Padding is content-aware and derived from which slots are filled, so it is not a choice you make separately — and an icon-only button is where the aria-label above stops being optional.
| Prop | Type | Default | Notes |
|---|---|---|---|
variant | "primary" | "secondary" | "outlined" | "danger" | "primary" | What the action is worth. See the variant table above. |
emphasis | "depth" | "solidFlat" | "ghost" | "solidFlat" | How much room it gets to say it. Independent of variant. |
size | "regular" | "large" | "regular" | 36px or 52px. Geometry only — never changes intent. |
rounding | "default" | "full" | "default" | 12px, or a pill. |
iconLeft | ReactNode | — | Glyph before the label. Sized to 20px, and the padding on that side tightens to balance it. |
iconRight | ReactNode | — | Glyph after the label. Same sizing and padding rule as iconLeft. |
iconOnly | boolean | false | Square button — 36px or 52px — whose children are the glyph. Pair it with an aria-label. |
disabled | boolean | false | Non-interactive, and repainted rather than faded. Also set by loading. |
loading | boolean | false | Shows a spinner, sets aria-busy and blocks interaction. |
asChild | boolean | false | Render the child element instead of a <button>, keeping the styling. |