Components / Interactive

IconButton

IconButton is the square, icon-only button. It shares Button's variant matrix and fixes the aspect ratio to a square — and it requires an aria-label, because the icon alone names nothing.

Loading playground…
Updated 3 days agoEdit on GitHubWeb & native

What it is

A Pressable with the same variant × intent × size matrix as Button, sized to a square and centring a single icon. The size token sets the box dimensions — an IconButton placed next to a same-size Button looks proportional.

aria-label is a required prop. The type system enforces it: there is no way to construct an IconButton without one.

Install

IconButton is exported from usemotif. No separate install.

example.tsx
import { IconButton } from 'usemotif';

API

IconButton

stable
function IconButton(props: IconButtonProps): JSX.Element
aria-labelstringrequired

Accessible label. The icon is `aria-hidden`, so this is the only name the button has.

iconReactNode

The icon to render. May also be passed as the single child.

variant"solid" | "outline" | "ghost"= "solid"

Visual weight — same axis as Button.

intent"primary" | "danger" | "success" | "neutral"= "primary"

Semantic colour — same axis as Button.

size"xs" | "sm" | "md" | "lg" | "xl"= "md"

Box dimensions (24 / 28 / 36 / 44 / 52px) and font size.

loadingboolean= false

Loading state. Suppresses clicks, sets `aria-busy`, and swaps the icon for a loading indicator.

…PressablePropsOmit<PressableProps, "children">

Every Pressable prop — `onPress`, `disabled`, style props, pseudo-state bags.

Variants

IconButton's matrix is Button's matrix — variant, intent, and size mean the same thing and take the same values. The one difference is size: on IconButton it sets a square box dimension rather than horizontal padding, since there is no label to pad around.

Accessibility

The icon is aria-hidden — its meaning is decorative as far as assistive technology is concerned. That makes aria-label the entire accessible name of the button, which is why it is required and not optional.

Write the label as the action, not the icon. "Delete" — not "trash can". "Close" — not "X". A screen-reader user hears the label; it should tell them what the button does, in the same words a visible text button would use.

Examples

A toolbar action:

example.tsx
<IconButton aria-label="Edit" onPress={edit}>
<Icon><path d="M4 20h4L18 10l-4-4L4 16z" /></Icon>
</IconButton>

A quiet, destructive variant:

example.tsx
<IconButton
aria-label="Delete item"
variant="ghost"
intent="danger"
size="sm"
onPress={remove}
>
<Icon><path d="M6 7h12M9 7V5h6v2M8 7l1 12h6l1-12" /></Icon>
</IconButton>

A row of icon buttons:

example.tsx
<HStack gap="$1">
<IconButton aria-label="Bold" variant="ghost"><BoldIcon /></IconButton>
<IconButton aria-label="Italic" variant="ghost"><ItalicIcon /></IconButton>
<IconButton aria-label="Underline" variant="ghost"><UnderlineIcon /></IconButton>
</HStack>