Skip to main content

Button

Triggers a single action or navigation event. Use the primary variant for the most important action in a context, secondary for supporting actions, and tertiary for low-emphasis actions.

OverviewStyleAccessibility

Description

Anatomy

PartRequired?Notes
Label Required (or icon) Sentence case. Verb + object preferred. 1–3 words, 5 maximum.
Leading icon Optional 16×16px. Reinforces intent — don't use decoration-only icons.
Trailing icon Optional 16×16px. Use for affordances like dropdowns (chevron) or external links.
Loading indicator Optional Spinner replaces or accompanies label during async operations.

Variants

Intent variants

Sizes

States

State Behaviour
Default Resting state. Full colour, full opacity.
Hover Slightly darker or lighter depending on variant. Cursor — pointer.
Focus 2px solid focus ring, 2px offset. Always visible — never suppressed.
Disabled Avoid where possible — disabled buttons are not in the tab order and give users no feedback about why the action is unavailable. Prefer showing the button in an error or inactive state with an explanation. When disabled is necessary: 50% opacity, cursor: not-allowed. Use aria-disabled="true" instead of the HTML disabled attribute if the button must remain keyboard-reachable.
Active / Pressed Depressed visual on press.
Loading Spinner visible. Button dimensions preserved. Optional label persists ("Saving…").

Usage guidelines

How to choose a button

Every button's role comes from two independent decisions: how much visual weight it should carry (appearance), and what it means semantically (variant). Pick both, then check the component reference below for the exact wording of that combination's role.

1. Appearance — how much weight?

Appearance Look Choose this when…
Accent Solid fill This is the one committed action in a self-contained decision point — a view's main call-to-action, or a dialog's confirming action.
Filled-Outlined Tinted, light fill The action relates to the primary task but shouldn't compete with it.
Outlined Bordered, no fill A standard action that needs to be visible but not prominent.
Plain Text only A dismissive, cancel, or low-stakes inline action.

2. Variant — what does it mean?

Variant Meaning
Neutral The default — no brand or intent meaning attached. This is where most buttons in the system live.
Brand Reserved for a small set of extremely high-value actions — not a general-purpose "primary" colour.
Danger Destructive or irreversible
Success Confirms or completes a positive action
Warning Proceeding despite a caution
Info Acknowledging or dismissing informational content

The rule that trips people up: Neutral/Accent is the default primary, not Brand/Accent

Accent is a visual weight, not a colour-specific thing — a solid-fill button competes with any other solid-fill button nearby, regardless of colour. Practically, that means:

  • Neutral/Accent is the default primary button. It's what most views and dialogs should reach for — the main save/submit/confirm/create action. It reads as solid and final without needing any brand emphasis.
  • Brand/Accent is not the everyday "make this the important button" upgrade — it's reserved for a small set of extremely high-value, high-stakes actions (e.g. publishing something publicly, completing a major milestone). If you're reaching for Brand/Accent just because an action feels like "the" primary action in an otherwise ordinary view, use Neutral/Accent instead.

Whichever one is present, it's capped at one Accent-tier button per view — if a second action still needs to feel final rather than soft, drop it to Filled-Outlined or Outlined rather than adding a second Accent button of any colour. Two Accent-tier buttons side by side will always compete for attention.

Note: appearance="filled" is a separate, valid value outside this role system — it's used for pressed/active states in segmented controls and toggle button groups (see ButtonGroup), not for standalone button roles.

Component reference

Neutral

Component Role
Neutral / Accent The default primary button — a solid, high-commitment action for most views and dialogs. Start here unless the action is Brand/Accent-worthy (see below) or clearly secondary.
Neutral / Filled-Outlined A brand-coloured action with reduced visual weight, tied to the primary flow but not competing with it — e.g. a secondary option alongside the main CTA that's still part of the same task.
Neutral / Outlined A standard secondary action that needs to be distinguishable but not prominent.
Neutral / Plain The lowest-emphasis action available — dismissive, cancel, or "skip this."

Brand

Component Role
Brand / Accent Reserved for extremely high-value actions — not the everyday primary button. Use sparingly, and never alongside another Accent-tier button.

Brand/Filled-Outlined and Brand/Outlined are not currently in the system — considered and deliberately excluded, since Neutral/Filled-Outlined already covers "tied to the primary flow, reduced weight," and no concrete screen has needed a third brand-weighted step. Revisit if a real use case comes up.

Danger

Component Role
Danger / Accent A destructive or irreversible action (delete, remove). Highest warning weight — solid fill in the danger colour. Should almost always sit behind a confirmation step.
Danger / Outlined A destructive action surfaced inline — a row, a card, a toolbar — without a dialog to carry the weight, or as a secondary option next to a Danger/Accent confirming action.
Danger / Plain A text-only destructive action with the lowest warning weight, used inline where a full-weight danger button would be excessive. Pending Ysa's audit for the same reason.

Success

Component Role
Success / Accent The confirming action inside a dialog or notification modal for a positive, completing action — e.g. "Publish," "Mark complete."
Success / Outlined A positive action surfaced inline, or a secondary option next to a Success/Accent confirming action.
Success / Plain A low-emphasis, inline positive action.

Warning

Component Role
Warning / Accent The confirming action inside a dialog that proceeds despite a caution — e.g. "Continue anyway," "Overwrite."
Warning / Outlined A cautionary action surfaced inline, or a secondary option next to a Warning/Accent confirming action.
Warning / Plain A low-emphasis, inline cautionary action.

Info

Component Role
Info / Accent The acknowledging action inside an informational dialog or notification modal — e.g. "Got it," "Learn more."
Info / Outlined An informational action surfaced inline, or a secondary option next to an Info/Accent action.
Info / Plain A low-emphasis, inline informational action.

Note: rendered as variant="brand" data-color="info" — this installed version of Web Awesome has no native "info" button variant. See component-overrides.css for the mapping.

Content guidelines

  • Sentence case. "Save changes," not "Save Changes" or "SAVE CHANGES."
  • *Product entity names stay capitalised (pending review).* Stakeholder, Property, Event, Consultation, Response (Open Point) and Project, Team, Team Leader, Campaign (Social Point) are capitalised in button labels, as with any other officially-named product term: "Add Stakeholder," "Create Property," "Delete Event," not "add a stakeholder," "create a property," "delete an event." This is the current standard, applied consistently across both products — see Capitalisation for the full reasoning and its open questions, which are still under review with marketing.
  • Verb + object. Tell the user what happens: "Delete Project," "Add Stakeholder," "Send invitation." Avoid vague labels like "OK," "Yes," or "Submit" — the label should tell users exactly what will happen without needing to read surrounding context.
  • 1–3 words, 5 maximum. Longer labels wrap or truncate at small sizes and slow down scanning.
  • State the consequence directly for destructive actions. "Delete Consultation," not "Remove?" or "Are you sure?" — the confirmation dialog, not the button label, is where you explain what's irreversible.
  • Match the label to the actual outcome. If a "Save" button also triggers a validation step, "Save" still describes the outcome correctly — but if it navigates elsewhere or triggers a side effect the user wouldn't expect, name that instead.
  • Loading labels use present continuous. "Saving…," "Deleting…," "Publishing…" — not "Save" with a spinner, which reads as unresponsive rather than in progress.
  • Never rely on colour alone to convey meaning. A Danger button's label should already say "Delete," "Remove," or "Revoke" — the red colour reinforces the label, it doesn't replace it.

Do / Don't

Do

Use sentence case for button labels, capitalising product entity names: "Save changes", "Add Stakeholder", "Delete Project".

Don't

Use vague labels like "OK", "Yes", or "Submit". The label should tell users exactly what will happen.

Do

Preserve button dimensions during loading states — prevent layout shift when the spinner appears.

Don't

Soften destructive actions with vague labels like "Remove?" — state the consequence directly: "Delete Consultation".

Do

Use aria-disabled="true" on buttons that should remain keyboard-reachable even when inactive.

Don't

Place multiple primary buttons in the same context. If two actions feel equally important, reconsider the hierarchy.

Layout & Spacing

Buttons use horizontal padding from the spacing scale. Heights are fixed per size to ensure consistent alignment with other form elements.

Size Height Padding (H) Gap Font size Min touch target
Extra small 24px 8px 4px 12px 44×44px (add invisible target zone)
Small 32px 12px 8px 14px 44×44px (add invisible target zone)
Medium (default) 40px 16px 8px 16px 44×44px native
Large 48px 20px 12px 16px 48×48px native
Extra large 56px 24px 12px 20px 56×56px native

These values come from the --orbit-button-size-{xs|s|m|l|xl}-* tokens, listed below. The heights are shared with inputs and selects through --orbit-size-control-*, so a button and a field line up in a row.

Component tokens

Button's colours, sizes and motion are published as framework-neutral CSS variables, so any framework (including React Aria components) can use them without Web Awesome. Colours follow Light and Dark mode and the product theme automatically. Every one aliases a Semantic token, never a Primitive.

Token What it sets
--orbit-button-{variant}-{appearance}-background Fill. Defined for the Accent appearances (Brand, Neutral, Danger, Info, Success, Warning) and for Neutral Filled-Outlined.
--orbit-button-{variant}-{appearance}-border Outline. Defined for the Outlined appearances and Neutral Filled-Outlined.
--orbit-button-{variant}-{appearance}-text Label and icon colour. Defined for every variant and appearance.
--orbit-button-state-hover, -active, -selected Translucent overlay laid over the fill for each state. Selected is the stronger overlay used for the chosen segment in a button group.
--orbit-button-disabled-background, -border, -text Disabled colours, shared by every variant.
`--orbit-button-size-{xs s
--orbit-button-radius, --orbit-button-radius-pill Corner radius. Pill is kept for now but may be removed.
--orbit-button-border-width Outline width.
`--orbit-button-focus-ring-{width offset
--orbit-button-transition-duration, -easing Motion. Durations collapse to near zero under reduced motion.
--orbit-button-font-family, -font-weight Label type.

States are overlays, not separate colours: hover, active and selected draw the overlay token over the fill rather than defining a colour per state.

Full-width buttons (width: 100%) are appropriate in forms, mobile viewports, and single-column card layouts. Avoid them in toolbars or inline contexts.

Tokens

PartTokenValue
Strong background (accent/filled) --wa-color-fill-loud Set per variant via component-overrides.css. Brand → --op-color-green-60 (#BACD42)
Soft background (outlined/subtle) --wa-color-fill-quiet Set per variant. Neutral → transparent at rest
Border --wa-color-border-loud Set per variant via variants.styles.ts. Neutral outlined → --wa-color-neutral-border-loud (#726d66) — not --op-color-border-default, which is Orbit's own site-chrome token and unrelated to this component
Text on strong background --wa-color-on-loud Brand → --op-color-neutral-05 (#101C07). Inverse → #fff
Text on soft background --wa-color-on-quiet Neutral → --op-color-text-primary. Inverse → #fff
Focus ring --wa-color-focus Currently hardcoded to #24a4fc (blue) in wa-theme.generated.css — doesn't yet resolve from --op-color-interactive-focus/--op-color-green-60 as documented here. Flagged for the token pipeline, not yet fixed.
Disabled opacity — 0.5
Border radius --wa-border-radius-m --op-radius-md → 8px

Engineering notes

  • Always use <button> for actions, not <a> or <div>. Buttons are keyboard-focusable and activate with both Enter and Space by default.
  • Prefer type="button" on all non-submit buttons inside forms to prevent accidental form submission.
  • For icon-only buttons, always provide an accessible label — either visible text, aria-label, or aria-labelledby.
  • Avoid using pointer-events: none as a disabled pattern — it removes keyboard accessibility. Use the disabled attribute or aria-disabled="true" instead.
  • For async actions (save, submit), disable or show a loading state on click to prevent double-submission. Preserve button dimensions during loading to avoid layout shift.

Keyboard interaction

KeyAction
Tab Moves focus to the button. Skips buttons with the HTML disabled attribute — use aria-disabled="true" if the button must remain reachable.
Shift + Tab Moves focus to the previous focusable element.
Enter Activates the button.
Space Activates the button.

Why it matters

A significant proportion of Open Point and Social Point users navigate by keyboard or assistive technology. A button that can only be activated with a mouse excludes these users entirely.

Focus

All button variants display a 2px solid focus ring in --wa-color-focus with a 2px offset, visible on both light and dark backgrounds. The ring must have at least 3:1 contrast against colours adjacent to it.

Use :focus-visible rather than :focus to suppress the ring on mouse click while preserving it for keyboard navigation.

After activation, focus must follow the outcome:

  • Opening a dialog → focus moves to the first focusable element inside the dialog.
  • Closing a dialog → focus returns to the button that triggered it.
  • No context change (e.g. toggling state) → focus stays on the button.

Watch out

The most common button accessibility mistake is removing the focus outline for visual polish without replacing it. If the default ring doesn't fit your design, replace it — never remove it. A button with no visible focus state is unusable for keyboard users.

ARIA

Role or attributeWhen to useExample
aria-label Icon-only buttons. The label must describe the action, not the icon name. aria-label="Close dialog"
aria-pressed Toggle buttons with two states (on/off, active/inactive). Update dynamically when state changes. aria-pressed="true"
aria-expanded Buttons that show or hide content (dropdowns, accordions, drawers). aria-expanded="false"
aria-controls Associates a button with the panel it controls. Pair with aria-expanded. aria-controls="filter-panel"
aria-haspopup Menu buttons that open a menu, listbox, tree, grid, or dialog. aria-haspopup="menu"
aria-disabled="true" When a button must remain keyboard-focusable but must not activate. Prevent the action in the event handler. aria-disabled="true"
aria-describedby Connects a button to a nearby explanation when the label alone doesn't communicate the consequence. aria-describedby="delete-warning"

How to apply it

If a button has a visible text label, the accessible name must contain that exact text — either as the button's content or within aria-label. When the two don't match, voice control users can't activate the button by speaking what they see, and screen reader users hear something different from what's on screen.

Contrast

Normal-weight text requires 4.5:1 contrast. Large text (18px regular or 14px bold) requires 3:1. Focus indicators and UI boundaries require 3:1 against adjacent colours. Disabled states are exempt.

Variant Text / Background Contrast ratio Result
Primary #101C07 on #BACD42 8.1:1 Pass
Secondary label #BACD42 on white ~3.7:1 Fails for normal-weight text under 18px — large text only
Destructive #FFFFFF on --op-color-red-40 4.6:1 Pass
Disabled (all variants) 50% opacity applied Varies Exempt

Watch out

The secondary label contrast (~3.7:1) is insufficient for normal-weight text under 18px. This must be resolved before the component is marked stable. Never use colour alone to distinguish button states — always pair a colour change with another visual indicator such as an icon, label, or pattern change.

Touch targets

The minimum touch target is 24×24px. The recommended target is 44×44px — use this as the standard for all interactive elements.

The medium button (40px height) meets the minimum and approaches the recommendation with horizontal padding included. The small variant (32px height) requires an invisible touch target extension to reach 44×44px.

On Social Point community-facing surfaces, prefer medium or large buttons. Community participants include people with motor impairments for whom small tap targets are a significant barrier.

Things to avoid

  • Using a non-button element for a button action — <div>, <span>, or <a> styled to look like a button won't be announced correctly and won't respond to Space or Enter. Use a native <button>.
  • Removing focus indicators without replacement — outline: none or outline: 0 with no equivalent visible focus style makes keyboard navigation impossible.
  • Accessible name that doesn't match the visible label — if a button has visible text, the programmatic name must contain that text. When they differ, voice control users can't activate the button by speaking what they see.
  • Colour alone to convey state — a colour change is not enough to show that a button is active, selected, or in an error state. Always pair it with another visual differentiator: an icon, a pattern, or a text change.
  • Changing the visible label on a toggle button — when using aria-pressed, only the attribute should change. Swapping the label alongside it creates a mismatch between what the screen reader announces and what the user sees.
  • Pointer-only interactions — binding only a click handler without keyboard equivalents means keyboard users can't activate the button.
  • A focus ring with insufficient contrast — the ring must have at least 3:1 contrast against colours adjacent to it, both on the button surface and the page background.

Was this page helpful?

Updated 5 October 2026