Variants
Accent
The single highest-emphasis icon action on a page. Restricted to lg and xl sizes; defaults to xl.
<IconButton icon={MaterialSearch} aria-label="Search" variant="accent" />Primary
Important icon actions that aren't the page-level CTA.
<IconButton icon={MaterialSearch} aria-label="Search" variant="primary" />Secondary
Paired with a primary to offer an alternative. Has a visible border.
<IconButton icon={MaterialFavorite} aria-label="Add to favourites" variant="secondary" />Tertiary
Low-emphasis icon actions — toolbar toggles, supplementary controls. Transparent background.
<IconButton icon={MaterialSettings} aria-label="Settings" variant="tertiary" />Inverse
Inside an inverse <Surface> this happens automatically — Surface is the source of truth. Pass inverse explicitly only for a dark or branded background that isn’t a Surface. Toggle to dark theme to see the canvas context.
Accent inverse
<Surface tone="inverse" className="rounded-lg p-6">
<IconButton icon={MaterialSearch} aria-label="Search" variant="accent" inverse />
</Surface>Inside an inverse Surface
Surface is the source of truth: inside an inverse Surface the icon button adopts inverse styling automatically — no inverse prop needed. Pass inverse explicitly only on a dark background that isn't a Surface. (The rendered preview below passes inverse explicitly as a docs-site-only workaround — this ComponentPreview's client:load island doesn't carry Surface's tone context to nested framework components, so the automatic adoption shown in the code sample can't run here. Storybook and real consumer apps pick it up from Surface automatically.)
<Surface tone="inverse" className="rounded-lg p-6 flex gap-4">
<IconButton icon={MaterialHome} aria-label="Home" variant="primary" />
<IconButton icon={MaterialFavorite} aria-label="Favourites" variant="secondary" />
<IconButton icon={MaterialSettings} aria-label="Settings" variant="tertiary" />
</Surface>Sizes
The shell is always square. accent only accepts lg and xl, so the size ramp below uses primary.
Small
40 × 40 px. Dense layouts, data tables, inline actions.
<IconButton icon={MaterialSearch} aria-label="Search" variant="primary" size="sm" />Medium
48 × 48 px. The default size for most layouts.
<IconButton icon={MaterialSearch} aria-label="Search" variant="primary" size="md" />Large
56 × 56 px. Larger touch targets and prominent actions.
<IconButton icon={MaterialSearch} aria-label="Search" variant="primary" size="lg" />Extra large
64 × 64 px. Page-level CTAs. The default size for the accent variant.
<IconButton icon={MaterialSearch} aria-label="Search" variant="primary" size="xl" />States
Disabled
Applied when the action is permanently unavailable in the current state.
<IconButton icon={MaterialSearch} aria-label="Search" variant="primary" disabled />Loading
Replaces the glyph with a spinner while an async operation is in progress. The accessible label is retained.
<IconButton icon={MaterialSearch} aria-label="Searching…" variant="primary" loading />When to use
Use an icon button for a compact action where a recognisable glyph communicates the intent on its own — search, favourite, settings, close. Because there is no visible text, the glyph must be unambiguous and you must always provide an accessible label for assistive technology.
Prefer a text Button when the action’s meaning isn’t obvious from an icon alone, or when you have room for a label. Icon buttons trade clarity for compactness, so reserve them for well-understood, conventional actions.
Variants
- Accent: the single most important icon action on a page. One accent button per viewport. Restricted to
lgandxlsizes. - Primary: important icon actions that aren’t the page-level CTA. One primary per distinct area.
- Secondary: supporting actions paired alongside a primary. Has a visible border.
- Tertiary: low-emphasis icon actions — toolbar toggles, supplementary controls. Transparent background.
Do
Give every icon button a meaningful aria-label describing the action.
Don't
Don't rely on an ambiguous or unfamiliar glyph alone to convey the action — pick an icon users will recognise.
Sizes
The shell is square at every size. accent is locked to lg and xl.
| Size | Dimensions | Glyph | Typical use |
|---|---|---|---|
xl | 64 × 64 px | 32 px | Page-level CTAs; default and largest for accent |
lg | 56 × 56 px | 28 px | Larger touch targets, prominent actions; smallest for accent |
md | 48 × 48 px | 24 px | Default for most layouts |
sm | 40 × 40 px | 22 px | Compact UI — data tables, inline actions |
Default size is xl for accent and md for all other variants.
When not to use
- Actions whose meaning isn’t obvious: use a text
Buttonwith a clear label instead. - Navigation: use a link (
<a>on web, or your app’s navigation primitive on native). Buttons are for actions, not destinations. - Multiple accent or primary icon buttons: if everything is high-emphasis, nothing is.
Props
Standard platform props also pass through to the rendered element (ButtonHTMLAttributes on web, PressableProps on native) — e.g. onMouseEnter/hitSlop, id/nativeID, aria-*. className/style stay blocked, along with the interaction/state props IconButton manages itself (type, aria-busy, onHoverIn/onFocus/etc).
| Prop | Type | Default | Description |
|---|---|---|---|
| icon * | ComponentType<SVGProps<SVGSVGElement>> (web) / ComponentType<SvgProps> (native) | - | Glyph component from @checkatrade/icons. Rendered hidden from assistive technology — the label carries the meaning. |
| aria-label * | string | - | Web — required. There is no visible text, so the accessible name must be provided explicitly. |
| accessibilityLabel * | string | - | Native — required. The accessible name announced by screen readers. |
| variant | 'accent' | 'primary' | 'secondary' | 'tertiary' | 'primary' | Visual style. accent is restricted to size lg or xl at the type level. |
| size | 'sm' | 'md' | 'lg' | 'xl' | 'xl' for accent, 'md' for all others | Controls the square shell and glyph size. accent only accepts lg or xl. |
| inverse | boolean | surrounding Surface tone | Swaps all colours to their inverse token equivalents. Defaults to the surrounding Surface tone — inverse inside `<Surface tone="inverse">`. Pass explicitly to force it on/off, e.g. on a dark background that is not a Surface. |
| disabled | boolean | false | Prevents interaction and applies disabled token colours. |
| loading | boolean | false | Shows a spinner in place of the glyph, disables interaction, and sets aria-busy="true" (busy on native). The accessible label is retained. |
| onClick | () => void | - | Web — called on click. No-op when disabled or loading. |
| onPress | () => void | - | Native — called on press. No-op when disabled or loading. |
| data-testid | string | - | Web — passed to the root button for test targeting. |
| testID | string | - | Native — passed to the root Pressable and the spinner for test targeting. |
Import
Web
import { IconButton } from '@checkatrade/components-web';
import { MaterialHome } from '@checkatrade/icons/react';Native
import { IconButton } from '@checkatrade/components-native';
import Home from '@checkatrade/icons/rn/home';Basic usage
<IconButton icon={MaterialHome} aria-label="Home" variant="accent" />
<IconButton icon={MaterialSearch} aria-label="Search" variant="primary" />
<IconButton icon={MaterialFavorite} aria-label="Add to favourites" variant="secondary" />
<IconButton icon={MaterialSettings} aria-label="Settings" variant="tertiary" />Inverse on a dark background
<div
style={{ background: 'var(--color-background-surface-brand-primary-default)', padding: '2rem' }}
>
<IconButton icon={MaterialHome} aria-label="Home" variant="accent" inverse />
</div>Loading state
<IconButton icon={MaterialSearch} aria-label="Searching…" 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 required: there is no visible text, so the accessible name must always be provided.- The glyph is rendered with
aria-hidden="true"so it isn’t announced a second time — the label alone carries the meaning. - Loading state sets
aria-busy="true", and thearia-labelis retained as the accessible name while the spinner is shown. - 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). accessibilityLabelis required: it provides the accessible name announced by screen readers.- The glyph is wrapped with
accessibilityElementsHiddenandimportantForAccessibility="no-hide-descendants"so it isn’t announced separately from the label. - Loading state sets
accessibilityState={{ busy: true }}.
Both platforms
- Minimum dimensions are
40 × 40px(sm), meeting WCAG 2.5.5 (AAA) touch target guidance at every size. accentis locked tolg | xlto ensure the page-level CTA always has sufficient visual weight and touch target size.
No releases yet.