Action
Button
BetaTriggers an action or event, available on web (React) and native (React Native). Supports four variants, four sizes, inverse rendering for branded backgrounds, and disabled and loading states.
Variants
Accent
The single highest-emphasis action on a page — 'Get quotes', 'Book now'. Only available at xl size.
<Button label="Get quotes" />Primary
Important actions that aren't the page-level CTA — form submissions, confirmations.
<Button label="Continue" variant="primary" />Secondary
Paired with a primary to offer an alternative — 'Cancel', 'Save draft'. Has a visible border.
<Button label="Save draft" variant="secondary" />Tertiary
Low-emphasis actions — inline alternatives, toolbar actions. Transparent background.
<Button label="Learn more" variant="tertiary" />Inverse
Use inverse on dark or branded backgrounds. Toggle to dark theme to see the canvas context.
Accent inverse
<Button label="Get quotes" inverse />Primary inverse
<Button label="Continue" variant="primary" inverse />Secondary inverse
<Button label="Save draft" variant="secondary" inverse />Tertiary inverse
<Button label="Learn more" variant="tertiary" inverse />Sizes
Small
Dense layouts, tables, or inline with other compact elements.
<Button label="Get quotes" variant="primary" size="sm" />Medium
The default size for most layouts.
<Button label="Get quotes" variant="primary" />Large
Prominent form actions and larger touch targets.
<Button label="Get quotes" variant="primary" size="lg" />Extra large
Page-level CTAs. The only size available for the accent variant.
<Button label="Get quotes" variant="primary" size="xl" />States
Disabled
Applied when the action is permanently unavailable in the current state.
<Button label="Continue" variant="primary" disabled />Loading
Replaces the label with a spinner while an async operation is in progress.
<Button label="Submitting" variant="primary" loading />Full width
Stretches to fill its container. Useful for mobile form submissions or stacked button groups.
<Button label="Get quotes" fullWidth />When to use
Use a button to trigger an action or transition. Keep labels short and action-oriented — favour verbs like “Save”, “Submit”, or “Get quotes” over vague terms like “Click here”.
Variants
- Accent: the single most important action on a page — “Get quotes”, “Book now”. One accent button per viewport. Locked to
xlsize. - Primary: important actions that aren’t the page-level CTA — form submissions, confirmations. One primary per distinct area.
- Secondary: supporting actions paired alongside a primary — “Cancel”, “Save draft”. Has a visible border.
- Tertiary: low-emphasis actions — inline alternatives, toolbar items, supplementary links-as-buttons.
Do
Use correct hierarchy: one high-emphasis CTA with a lower-emphasis alternative.
Don't
Use multiple high-emphasis buttons at the same level — it removes visual hierarchy.
Sizes
| Size | Typical use |
|---|---|
xl | Page-level CTAs; the only size for accent |
lg | Prominent form actions, larger touch targets |
md | Default for most layouts |
sm | Compact UI — data tables, inline actions |
Full width
Use fullWidth for mobile form submissions or stacked button groups where the button should span the container. Avoid it in desktop contexts where a natural content-width button is more appropriate.
When not to use
- Navigation: use a link (
<a>on web, or your app’s navigation primitive on native) instead. Buttons are for actions, not destinations. - Destructive actions without confirmation: pair with a confirmation dialog.
- Multiple accent or primary buttons: if everything is high-emphasis, nothing is.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| label * | string | - | Button text. Also used as the accessible name via aria-label. |
| variant | 'accent' | 'primary' | 'secondary' | 'tertiary' | 'accent' | Visual style. accent is restricted to size xl at the type level. |
| size | 'sm' | 'md' | 'lg' | 'xl' | 'xl' for accent, 'md' for all others | Controls padding, font size, and border radius. |
| inverse | boolean | false | Swaps all colours to their inverse token equivalents. Use on dark or branded backgrounds. |
| fullWidth | boolean | false | Stretches the button to fill its container width. |
| disabled | boolean | false | Prevents interaction and applies disabled token colours. |
| loading | boolean | false | Shows a spinner, disables interaction, and sets aria-busy="true". The label is retained as the accessible name. |
| onClick | () => void | - | Web — called on click. No-op when disabled or loading. |
| onPress | () => void | - | Native — called on press. No-op when disabled or loading. |
| accessibilityLabel | string | - | Native — overrides the default accessible name (which falls back to label). |
| testID | string | - | Native — passed to the root Pressable and the spinner for test targeting. |
Import
Web
import { Button } from '@checkatrade/components-web';Native
import { Button } from '@checkatrade/components-native';Basic usage
<Button label="Get quotes" />
<Button label="Continue" variant="primary" />
<Button label="Save draft" variant="secondary" />
<Button label="Cancel" variant="tertiary" />Inverse on a dark background
<div style={{ background: 'var(--color-background-brand-primary)', padding: '2rem' }}>
<Button label="Get quotes" inverse />
</div>Full width
<Button label="Submit" variant="primary" fullWidth />Loading state
<Button label="Submitting" variant="primary" loading /> Platform status
| Platform / Area | Status |
|---|---|
| Design (Figma) | Beta |
| Web (React) | Beta |
| Native (React Native) | Beta |
| iOS (Swift) | Planned |
| Android (Kotlin) | Planned |
| Accessibility audit | Planned |
Accessibility
Web
- Uses a native
<button>element — keyboard accessible by default. aria-labelis set from thelabelprop, so the accessible name is always present even when the label is visually replaced by a spinner during loading.- Loading state sets
aria-busy="true". - Focus ring is provided globally via
@checkatrade/tokens/css/base—button:focus-visiblegets a 3px solid outline with 2px offset. The inverse focus ring switches automatically viadata-inverseon the underlying element.
React Native
- Uses
PressablewithaccessibilityRole="button"— announced as a button by screen readers on iOS (VoiceOver) and Android (TalkBack). accessibilityLabeldefaults tolabel, so the accessible name is always present. Override with theaccessibilityLabelprop when the visible label needs context.- Loading state sets
accessibilityState={{ busy: true }}. - Hover state (
onHoverIn/onHoverOut) is active on react-native-web for web users; it is a no-op on iOS and Android.
Both platforms
- Minimum height is
44px, meeting WCAG 2.5.5 (AAA) touch target guidance. accentis locked toxlto ensure the page-level CTA always has sufficient visual weight and touch target size.
Button Changelog
2026-05-08 — fullWidth and hover (PR #26)
Added
fullWidthprop — stretches the button to fill its container width.
2026-05-07 — Code review (PR #18)
Changed
- Focus ring, cursor, and font-weight moved out of component classes into
@checkatrade/tokens/css/base. The component no longer hardcodes these — they are provided by a single import at the app level. data-inverseattribute added to the underlying<button>element sobase.csscan target the inverse focus ring colour viabutton[data-inverse]:focus-visible.defaultVariants.sizeremoved — it was dead code, always overridden byresolvedSize.- Redundant
onClickguard removed —<button disabled>natively prevents click events.
Added
- TypeScript discriminated union:
variant="accent"now enforcessize="xl"at compile time. Passing a smaller size to an accent button is a type error. - Storybook:
onPressaction wired viaargTypesinstead of inline in each story arg.Accentstory locks size control.AllVariants,AllVariantsInverse, andAllSizesstories added.
Fixed
AllVariantsstory was mixing sizes; all variants now shown atxlfor consistent comparison.
2026-04-23 — Initial implementation (PR #2, #15)
Added
Buttoncomponent withaccent,primary,secondary,tertiaryvariants.sm,md,lg,xlsizes with token-backed padding and border radius.inverse,disabled,loadingprops.resolvedSizelogic:accentdefaults toxl, others tomd.- Full token coverage: colours, spacing, typography via Tailwind utilities.
- Storybook stories:
Accent,Primary,Secondary,Tertiary,Disabled,Loading,AllVariants,AllVariantsInverse,AllSizes. - Tests: rendering, interaction, disabled state, loading state (12 tests).
Button Changelog (Native)
2026-05-08 — fullWidth and hover (PR #26)
Added
fullWidthprop — stretches the button to fill its container viastyle={{ width: '100%' }}.- Hover state via
onHoverIn/onHoverOutonPressable— no-op on iOS/Android, active on react-native-web.
2026-05-07 — Alignment with web Button (PR #18)
Changed
- Variants renamed to match the web component:
accent | primary | secondary | tertiary(previouslyprimary | secondary | outline, where “primary” incorrectly used accent tokens and “outline” used tertiary tokens). - Styling rewritten using Unistyles v3
StyleSheet.create(theme => ...)withvariantsandcompoundVariantsinstead ofStyleSheet.flattenwith inline conditionals. effectiveVariantpattern removed — disabled state is now its own variant axis (isDisabled) rather than overriding the semantic variant.resolvedSizelogic aligned with web:accentdefaults toxl, others default tomd.- Spinner colour resolved directly from theme by props, not extracted from the resolved style object.
Added
xlsize with token-backed padding and border radius (spacingButtonXl*).inverseprop with compound variants covering all four variants and disabled state.- TypeScript discriminated union:
variant="accent"enforcessize="xl"at compile time. - Storybook:
onPressaction wired viaargTypes.AllVariants,AllVariantsInverse,AllSizesstories added. - Test: inverse prop rendering (2 tests). “Retains accessible name when loading” test added.
Fixed
- Font size and line height tokens were silently
undefined— keys weretypographyLabelFontSizeActionSm(non-existent). Corrected totypographyLabelFontSizeButtonSm/Md/Lg/Xl. - Unistyles mock updated to strip
variants/compoundVariantsfrom style objects and expose a no-opuseVariantsso tests run without errors.
2026-04-23 — Initial implementation (PR #2, #3)
Added
Buttoncomponent withprimary,secondary,outlinevariants (using accent, secondary, and tertiary tokens respectively).sm,md,lgsizes.disabledandloadingprops.accessibilityLabeloverride prop andtestIDsupport.registerThemehelper for Unistyles setup.- Tests: rendering, interaction, disabled state, loading state (11 tests).