Skip to content

Icon Button

Edit on GitHub
@volvo-cars/css v2.3.0@volvo-cars/react-icons v3.3.6

IconButton from @volvo-cars/react-icons renders a medium (default) 40×40 px button containing a 16×16 px icon. Every icon button requires aria-label or aria-labelledby since there is no visible text — the types enforce this.

import { IconButton } from '@volvo-cars/react-icons';
export function IconButtonVariants() {
return (
<div className="flex flex-wrap gap-16 items-center">
<IconButton icon="x" aria-label="Close" variant="filled" />
<IconButton icon="x" aria-label="Close" variant="outlined" />
<IconButton icon="x" aria-label="Close" variant="clear" />
</div>
);
}

High emphasis. Use on media surfaces (images, video) to guarantee contrast.

Medium emphasis. Default variant. Use when the button needs more prominence than clear but less than filled.

Low emphasis. Only use where context and placement make the interactive nature obvious, such as navigation bars.

  • destructive — delete, remove, or cancel actions. Available on all variants.
  • subtle — gray surface with primary text. Filled only.

Omit color for the default neutral appearance.

import { IconButton } from '@volvo-cars/react-icons';
export function IconButtonColors() {
return (
<div className="flex flex-wrap gap-16 items-center">
<IconButton icon="x" aria-label="Close" variant="filled" />
<IconButton
icon="trash-can"
aria-label="Delete"
variant="filled"
color="destructive"
/>
<IconButton
icon="cogs"
aria-label="Settings"
variant="filled"
color="subtle"
/>
</div>
);
}

Clear icon buttons in medium size (default) take up 40×40 px. Set bleed to add negative margin so the button visually aligns with surrounding content while retaining the full 40×40 px touch area and hover state. Only available on the clear variant.

With bleed

Without bleed

import { IconButton } from '@volvo-cars/react-icons';
export function IconButtonBleed() {
return (
<div className="flex-col gap-16">
<p className="font-medium font-14">With bleed</p>
<div className="flex gap-24">
<IconButton icon="heart" aria-label="Favorite" variant="clear" bleed />
<IconButton icon="share" aria-label="Share" variant="clear" bleed />
</div>
<p className="font-medium font-14">Without bleed</p>
<div className="flex gap-24">
<IconButton icon="heart" aria-label="Favorite" variant="clear" />
<IconButton icon="share" aria-label="Share" variant="clear" />
</div>
</div>
);
}

Pass href to render an <a> instead of a <button>:

import { IconButton } from '@volvo-cars/react-icons';
export function IconButtonLink() {
return (
<div className="flex gap-16 items-center">
<IconButton
icon="info-circled"
href="#info"
aria-label="More info"
variant="filled"
/>
<IconButton
icon="cogs"
href="#settings"
aria-label="Settings"
variant="outlined"
/>
<IconButton
icon="arrow-up"
href="#top"
aria-label="Back to top"
variant="clear"
/>
</div>
);
}

Use asChild to render onto a framework link component like Next.js Link:

import { IconButton } from '@volvo-cars/react-icons';
import Link from 'next/link';
<IconButton icon="cogs" aria-label="Settings" asChild>
<Link href="/settings" />
</IconButton>;

Buttons that toggle state should update aria-label to describe the resulting action rather than using aria-pressed. Set data-color-mode="dark" when the button sits on dark media.

import { IconButton } from '@volvo-cars/react-icons';
import { useState } from 'react';
export function IconButtonPlayPause() {
const [playing, setPlaying] = useState(false);
return (
<div data-color-mode="dark" className="bg-always-black p-24 shape-default">
<IconButton
icon={playing ? 'pause' : 'play'}
aria-label={playing ? 'Pause' : 'Play'}
variant="filled"
onClick={() => setPlaying(!playing)}
/>
</div>
);
}

Prefer the disabled prop (which sets aria-disabled) over the native disabled attribute — it keeps the button focusable for screen readers and prevents click events internally. Disabled icon buttons get reduced opacity automatically.

import { IconButton } from '@volvo-cars/react-icons';
export function IconButtonDisabled() {
return (
<div className="flex flex-wrap gap-16 items-center">
<IconButton icon="x" aria-label="Close" variant="filled" disabled />
<IconButton icon="x" aria-label="Close" variant="outlined" disabled />
<IconButton icon="x" aria-label="Close" variant="clear" disabled />
</div>
);
}

Pre-built icon buttons with translated accessible labels are available from @volvo-cars/react-icons/localized. These require @volvo-cars/react-locale-provider to be set up in your app.

import {
BackIconButton,
CloseIconButton,
ForwardIconButton,
NextIconButton,
PlayIconButton,
PlayPauseIconButton,
PrevIconButton,
} from '@volvo-cars/react-icons/localized';
import { LocaleProvider } from '@volvo-cars/react-locale-provider';
export function IconButtonLocalized() {
return (
<LocaleProvider locale="sv-SE">
<div className="flex flex-wrap gap-16 items-center">
<BackIconButton variant="outlined" />
<PrevIconButton variant="outlined" />
<NextIconButton variant="outlined" />
<ForwardIconButton variant="outlined" />
<CloseIconButton variant="outlined" />
<PlayIconButton variant="outlined" />
<PlayPauseIconButton variant="outlined" paused />
</div>
</LocaleProvider>
);
}

ShowPasswordIconButton and PlayPauseIconButton are stateful — their icon and accessible label change based on the passwordVisible / paused prop. Click to toggle:

Password hidden
Paused
import {
PlayPauseIconButton,
ShowPasswordIconButton,
} from '@volvo-cars/react-icons/localized';
import { LocaleProvider } from '@volvo-cars/react-locale-provider';
import { useState } from 'react';
export function IconButtonLocalizedInteractive() {
const [passwordVisible, setPasswordVisible] = useState(false);
const [paused, setPaused] = useState(true);
return (
<LocaleProvider locale="en">
<div className="flex gap-32 flex-wrap">
<div className="flex-col items-center gap-4">
<ShowPasswordIconButton
passwordVisible={passwordVisible}
onClick={() => setPasswordVisible(!passwordVisible)}
/>
<span className="micro">
{passwordVisible ? 'Password visible' : 'Password hidden'}
</span>
</div>
<div className="flex-col items-center gap-4">
<PlayPauseIconButton
paused={paused}
onClick={() => setPaused(!paused)}
/>
<span className="micro">{paused ? 'Paused' : 'Playing'}</span>
</div>
</div>
</LocaleProvider>
);
}

Available localized buttons:

ComponentIconPurpose
BackIconButtonarrow-backNavigate back
CloseIconButtonxClose dialogs/overlays
ForwardIconButtonarrow-forwardNavigate forward
NextIconButtonchevron-forwardNext item/page
PrevIconButtonchevron-backPrevious item/page
PlayIconButtonplayPlay media
PlayPauseIconButtonplay/pauseToggle play/pause
ShowPasswordIconButtoneyeToggle password visibility

These buttons automatically provide localized aria-label values based on the current locale from LocaleProvider.

Key consumer responsibilities from the WCAG audit:

  • Accessible name required — every icon button needs aria-label or aria-labelledby. The label should describe the action (“Close”, “Delete”), not the icon (“X”, “Trash”).
  • Sufficient contrast — icon and background must meet 4.5:1 contrast. Use filled buttons on media surfaces; never use clear or outlined on images or video.
  • Bleed spacing — when using bleed, ensure enough surrounding space for the full 40×40 px touch area.
  • Toggle buttons — for play/pause or similar toggles, update aria-label to describe the resulting action rather than the current state.
Prop Type Required Default
icon "code" | "link" | "map" | "menu" | "search" | "... -
The icon to render in the IconButton using the 1.0 icon system.
iconFilled boolean - -
Use the filled variant of the icon, typically for active or selected states.
iconLoading "eager" | "lazy" - lazy
The loading strategy for the icon image.
color "neutral" | "destructive" | "subtle" - neutral
The color of the IconButton
size "medium" | "large" - medium
The size of the IconButton. - `'medium'` — 40 px button, 16 px icon - `'large'` — 48 px button, 24 px icon
disabled boolean - -
Disables the button. Avoid using disabled buttons whenever possible - instead show error messages explaining the next steps the user should take.
className string - -
Custom class name, merged with existing classes.
hidden boolean - -
id string - -
title string - -
dir string - -
lang string - -
slot string - -
style CSSProperties - -
tabIndex number - -
variant "outlined" | "filled" | "clear" - outlined
The visual style of the IconButton.
bleed boolean - false
Allows the background to bleed out into the surrounding layout, leaving the button to only take up the space for the icon itself. Only supported on the `clear` variant.
aria-label string - -
Defines a string value that labels the current element. @see aria-labelledby.
aria-labelledby string - -
Identifies the element (or elements) that labels the current element. @see aria-describedby.
href string - -
onClick MouseEventHandler<HTMLButtonElement> - -
Called when the button is clicked. If used within a `<form>`, prefer to set the button to `type=submit` and use the `onSubmit` event on the form instead.
form string - -
Id of a form element that this button should be associated with. Defaults to the containing form element.
formAction string - -
The URL that processes the information submitted by the button, overriding the `action` attribute of the button's form.
formMethod "post" | "get" - -
Specifies the HTTP method used to submit the form, overriding the `method` attribute of the button's form.
name string - -
The name of the button, submitted as a pair with the button's value as part of the form data.
value string - -
The value associated with the button's `name` in the form data when the form is submitted using this button.
type "button" | "submit" - button
onBlur FocusEventHandler<HTMLButtonElement> - -
onFocus FocusEventHandler<HTMLButtonElement> - -
onKeyDown KeyboardEventHandler<HTMLButtonElement> - -
onKeyUp KeyboardEventHandler<HTMLButtonElement> - -
onPointerDown PointerEventHandler<HTMLButtonElement> - -
onPointerEnter PointerEventHandler<HTMLButtonElement> - -
onPointerLeave PointerEventHandler<HTMLButtonElement> - -
onPointerMove PointerEventHandler<HTMLButtonElement> - -
onPointerUp PointerEventHandler<HTMLButtonElement> - -
onAnimationEnd AnimationEventHandler<HTMLButtonElement> - -
onAnimationStart AnimationEventHandler<HTMLButtonElement> - -
onTransitionEnd TransitionEventHandler<HTMLButtonElement> - -
asChild boolean -