Skip to content

Action

Icon Button

Beta

An icon-only button for compact, recognisable actions, available on web (React) and native (React Native). Shares Button's variants, sizes, inverse rendering, and disabled and loading states, but uses a square fixed-size shell with no text label and a required accessible label.

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.

<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 lg and xl sizes.
  • 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.

SizeDimensionsGlyphTypical use
xl64 × 64 px32 pxPage-level CTAs; default and largest for accent
lg56 × 56 px28 pxLarger touch targets, prominent actions; smallest for accent
md48 × 48 px24 pxDefault for most layouts
sm40 × 40 px22 pxCompact 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 Button with 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

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 / AreaStatus
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-label is 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 the aria-label is retained as the accessible name while the spinner is shown.
  • Focus ring is provided globally via @checkatrade/tokens/css/basebutton:focus-visible gets a 3px solid outline with 2px offset. The inverse focus ring switches automatically via data-inverse on the underlying element.

React Native

  • Uses Pressable with accessibilityRole="button" — announced as a button by screen readers on iOS (VoiceOver) and Android (TalkBack).
  • accessibilityLabel is required: it provides the accessible name announced by screen readers.
  • The glyph is wrapped with accessibilityElementsHidden and importantForAccessibility="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.
  • accent is locked to lg | xl to ensure the page-level CTA always has sufficient visual weight and touch target size.

No releases yet.