{tokens} resolutionThe most prominent action on a surface. One per surface.
Lower-emphasis action. Pairs with a primary on the same surface.
Minimal-emphasis action. Used in toolbars, table rows, and dense surfaces.
Destructive action. Always paired with a confirmation step.
ARIA notes
Native <button type="button"> carries the role implicitly. Set aria-label when the visible label is not descriptive (icon-only buttons). Critical variants must be paired with a confirmation step (modal, dialog, or undo affordance) — destructive actions one click away from completion are an anti-pattern.
When to use Button
Use a button to trigger an action — submit a form, save a record, open a dialog, dismiss a notification, navigate to a destination that performs work on arrival. A button is for doing; for reading (cross-references, navigation between pages without state change) use a Link.
Variants
The substrate ships four button variants. Each has one canonical emphasis level and one canonical use context.
| Variant | Emphasis | Use for |
|---|---|---|
| Primary | Highest | The most important action on a surface. Form submit. Save. Confirm. Apply. One per surface. |
| Secondary | Lower | A complementary action paired with a primary. Cancel. Close. Back. |
| Ghost | Minimal | Inline actions in dense surfaces. Toolbar buttons. Row-level controls. Settings dropdowns. |
| Critical | High (destructive) | Destructive actions. Delete. Discard. Revoke. Always pair with a confirmation step. |
When to use
- Form submit. Always Primary. Label with the action verb
(
Save,Apply,Continue) — neverSubmit(vague) orOK(banned in PointSav register). - Cancel a destructive flow. Secondary. Label with the action
the user is taking (
Discard changes,Cancel). - Toolbar actions in a data grid. Ghost. Icon + label, or
icon-only with
aria-label. - Delete a record. Critical. Label specifies what is being
deleted (
Delete account, notDelete). Always opens a confirmation dialog.
When not to use
- Do not use a button for navigation between pages without state change. That is a Link.
- Do not use a button to surface information that is already visible. The button label IS the action; if there is no action, there is no button.
- Do not use multiple primary buttons on the same surface — only one canonical action per context.
- Do not use Critical for non-destructive actions. The visual weight is reserved.
Anatomy
A button is a single rectangular trigger with three internal elements:
- Container — background colour, border radius, focus ring.
- Label — utility-4 typography (16/24, 600 weight).
- Optional icon — leading or trailing the label, never both on the same button. Icon size matches the label cap height.
Sizes
v0.0.2 ships one canonical size: 40px height (min-height: 2.5rem).
This satisfies the WCAG 2.2 minimum touch target (44x44 with
focus-ring offset). Larger and smaller sizes are subsequent-
milestone work; the dense-grid case currently uses Ghost.
Live demo
Future: an interactive showcase here once the engine ships
<ps-component-demo> rendering. v0.0.2 ships the recipe;
contributors who consume the recipe render their own demo.
Behaviour
Focus
Buttons receive focus via Tab and Shift+Tab. The focus ring is
2px solid {semantic.focus-ring} with 2px offset, conformant to
WCAG 2.2 AAA against any tenant background.
Activation
Space and Enter both activate. Clicked buttons fire on pointerup
within their bounds; pointer-down outside the button cancels
activation.
Disabled
Disabled buttons render with {semantic.interactive-primary-disabled}
background and {semantic.ink-disabled} text. They are not
focusable. Avoid disabled states where possible — surface the
constraint near the trigger instead, so the user knows why they
cannot proceed.
Loading
Subsequent milestone — a loading button shows a spinner alongside the label and disables interaction without losing focus.
Brand voice
Button labels follow the substrate's voice rules from
themes/pointsav-brand.json:
- Direct. Action verb first.
Save changes, notClick here to save your changes. - Specific. What is being saved?
Save report,Save draft. - Confident. No hedging.
Continue, notContinue?. - Professional. No marketing register.
Get started, notLet's get started!.
Banned vocabulary applies to button labels per
themes/pointsav-brand.json voice.lexicon_avoid.