IconButton
A square, icon-only action — share, favourite, close, a menu trigger. Requires an aria-label, because the icon is not a name.
import IconButton from '@/components/ui/IconButton';Source: src/components/ui/IconButton.tsx
Examples
Sizes
sm 30px, md 40px, lg 50px. Two icon buttons sitting next to each other must be the same size: mismatched corner actions on the store page were a real bug, which is why that page names its size in one shared constant.
Colours and outline
unstyled — when the caller owns the surface
Removes the background, hover wash, text colour and corner radius, leaving the box: size, transition and the focus ring. It exists for one case — a caller that needs its OWN hover background, like the refer-a-friend card’s round social buttons, which fill with the network’s brand colour. A className can’t do that job: a caller’s hover:bg-* and the component’s own hover:bg-white are the same property at the same specificity, so which one wins is stylesheet order. The class has to be absent, not outranked (the same trap Card documents for its tones). Everything visible below is the caller’s.
Props
<IconButton> props
| Prop | Type | Default | Description |
|---|---|---|---|
size | 'sm' | 'md' | 'lg' | 'md' | — |
color | 'navy' | 'blue' | 'red' | 'green' | 'gold' | 'navy' | — |
shape | 'square' | 'auto' | 'square' | 'square' (default) is the fixed icon-sized hit target. 'auto' keeps the same height but sizes width to content, for children wider than one icon (e.g. an avatar plus a dropdown chevron). |
outline | boolean | false | Hairline border with a translucent white base that turns solid on hover — for icon buttons floating over imagery (card corner actions), where the chromeless default disappears into busy photos. |
unstyled | boolean | false | Drops the component's own background, hover wash, text colour AND corner radius, leaving only the box — size, transition and the focus ring. For the one case a className cannot serve: a caller that needs its OWN hover background (the brand-filled social share buttons, which fill with Facebook blue on hover). A caller's `hover:bg-*` and this component's `hover:bg-white` are the same property at the same specificity, so the winner is stylesheet order rather than source order — the same trap Card.tsx documents for its tones, which is why the class has to be absent rather than merely overridden. The caller then owns border, ground, glyph colour and radius outright — the radius for the same reason, since `rounded-full` on a caller would be racing this component's own `rounded-[3px]`, and a social button that loses that race is a rounded square rather than a circle. |
href | InternalHref | string | — | Renders the icon button as a link, like Button's `href`: internal routes go through the locale-aware next-intl Link, external URLs (https:, //, mailto:, tel:) open in a new tab. Combined with `disabled`, renders an inert button instead of a link. |
target | '_blank' | '_self' | — | Only meaningful with an internal `href`: opens the route in a new tab (`_blank`) instead of navigating this one. External hrefs already open in a new tab regardless. Sets `rel="noopener"` alongside `_blank`. Use it for a route whose JOB is to leave the site (the waiting page), never as a default for internal links. Same contract as Button's `target`. |
Also accepts everything in ButtonHTMLAttributes<HTMLButtonElement> — so native attributes (id, onClick, aria-*, data-*) pass straight through.
Accessibility
- aria-label is the accessible name; the icon is aria-hidden.
- For a two-state action (favourite, follow) use IconToggle instead — it manages aria-pressed.
Usage
Do
- Always pass aria-label. Without it the control is announced as “button” and nothing else.
- Match sizes between adjacent icon buttons, and share the value rather than repeating it.
- With an internal href, pass target="_blank" only for a route whose job is to leave the site — the store cards’ cart button opens the waiting page that way. External hrefs already open in a new tab.
Don’t
- Don’t use one for an action whose meaning isn’t obvious from the glyph — a labelled Button costs less than a mystery.
- Don’t nest an Icon of a different size than the button; the size props are meant to pair.
- Don’t reach for unstyled to restyle a button — only when you own the whole hover surface. If the default or outline surface is nearly right, use it; unstyled hands you the border, ground, glyph colour AND radius, and forgetting any one of them is a button that looks broken.