Action hierarchy
Every surface has exactly one obvious next step, every flow exactly one CTA. Rank everything else below.
What is a surface?
The unit the one-primary rule applies to: a card, a dialog, a form, a self-contained
section. Not the page — a page composes many surfaces, so a card grid legitimately
shows one default button per card. Within one surface there is never a second
default.
Rules
- One primary (
variant="default") per surface. When a second action appears, demote it tooutline/secondary— even conditionally (e.g. a campaign card flips its action tooutlinethe moment a “Pay” primary shows up next to it). size="cta"is the journey’s forward action — advance the step, create the first entity, pay. At most one visible per screen; screens outside the main flow usually have none.- In a multi-step flow the CTA lives in one persistent action bar (e.g. a fixed bottom bar — ghost icon Back on the left, CTA on the right). Individual screens don’t grow their own competing primary; even form submission is delegated to the bar.
- Everything else demotes: stand-alone neutral actions →
secondary; contextual utility/reset →outline; dismiss/close/toolbar →ghost. - Destructive actions require confirmation only when irreversible or costly — deleting persisted work, discarding unsaved changes, moving money. Recoverable, in-flow removals (remove from cart, remove a re-uploadable file) execute immediately; a confirm dialog there is friction, not safety.
- Gate, don’t hide. An unavailable primary stays visible and disabled, with the reason shown (tooltip listing blockers, readiness checklist). [research]
Placement
- Dialog / form footer — primary last in reading direction (right in LTR), cancel directly before it. [research]
- Cancel variant —
outlinein dialog footers,ghostin lightweight inline confirmations; neverdefault, neverdestructive. (Existing code predates this rule and is inconsistent — follow the rule, don’t copy the code.) - Cards — actions sit at the bottom of the card body, aligned left (start) with the content, primary first in reading order (the demoted action follows it); they stack vertically on narrow widths. Unlike dialog footers, nothing is pushed right — a card reads top-to-bottom and its action is part of that flow.
- Inline edit idiom — pencil trigger → input + ButtonGroup of two
outlineicon-smbuttons, confirm (✓) first, cancel (✕) second; Enter confirms, Escape cancels. - Collections — the collection’s primary action (“New campaign”) lives in the view’s persistent chrome (sidebar, list header) or in the empty state — never repeated per item.
- Empty state — guidance text plus the single CTA; never a wall of zeros.
Copy
What action labels and confirmations say — voice, sentence case, verb + object, error wording — lives in docs/patterns/copy.md.