# IconButton

> A square button for icon-only actions

Status: stable. Added in Components 0.1.0. Updated in Components 2.16.0, 20 Oct 2026.

A square button for icon-only actions.

## Import

```tsx
import { IconButton } from '@oztix/roadie-components'
```

## Examples

### Default

Always provide an `aria-label` since there is no visible text.

```tsx
<IconButton aria-label='Add item'>
  <Plus weight='bold' />
</IconButton>
```

### Emphasis

```tsx
<IconButton aria-label='Strong' emphasis='strong'>
  <Star weight='bold' />
</IconButton>
<IconButton aria-label='Normal' emphasis='normal'>
  <Star weight='bold' />
</IconButton>
<IconButton aria-label='Subtle' emphasis='subtle'>
  <Star weight='bold' />
</IconButton>
<IconButton aria-label='Subtler' emphasis='subtler'>
  <Star weight='bold' />
</IconButton>
```

### Sizes

The default size is `md`. All sizes maintain square proportions.

```tsx
<IconButton aria-label='Extra small' size='xs'>
  <Heart weight='bold' />
</IconButton>
<IconButton aria-label='Small' size='sm'>
  <Heart weight='bold' />
</IconButton>
<IconButton aria-label='Medium' size='md'>
  <Heart weight='bold' />
</IconButton>
<IconButton aria-label='Large' size='lg'>
  <Heart weight='bold' />
</IconButton>
```

The legacy `icon-xs` / `icon-sm` / `icon-md` / `icon-lg` size literals are still accepted for backwards compatibility (marked `@deprecated` in TypeScript) but new code should use the plain `xs` / `sm` / `md` / `lg` names above.

### Intents

```tsx
{/* Strong */}
<IconButton aria-label='Neutral' emphasis='strong' intent='neutral'><CheckCircle weight='bold' /></IconButton>
<IconButton aria-label='Accent' emphasis='strong' intent='accent'><CheckCircle weight='bold' /></IconButton>
<IconButton aria-label='Brand' emphasis='strong' intent='brand'><CheckCircle weight='bold' /></IconButton>
<IconButton aria-label='Info' emphasis='strong' intent='info'><CheckCircle weight='bold' /></IconButton>
<IconButton aria-label='Success' emphasis='strong' intent='success'><CheckCircle weight='bold' /></IconButton>
<IconButton aria-label='Warning' emphasis='strong' intent='warning'><CheckCircle weight='bold' /></IconButton>
<IconButton aria-label='Danger' emphasis='strong' intent='danger'><CheckCircle weight='bold' /></IconButton>
{/* Normal */}
<IconButton aria-label='Neutral' emphasis='normal' intent='neutral'><CheckCircle weight='bold' /></IconButton>
<IconButton aria-label='Accent' emphasis='normal' intent='accent'><CheckCircle weight='bold' /></IconButton>
<IconButton aria-label='Brand' emphasis='normal' intent='brand'><CheckCircle weight='bold' /></IconButton>
<IconButton aria-label='Info' emphasis='normal' intent='info'><CheckCircle weight='bold' /></IconButton>
<IconButton aria-label='Success' emphasis='normal' intent='success'><CheckCircle weight='bold' /></IconButton>
<IconButton aria-label='Warning' emphasis='normal' intent='warning'><CheckCircle weight='bold' /></IconButton>
<IconButton aria-label='Danger' emphasis='normal' intent='danger'><CheckCircle weight='bold' /></IconButton>
```

### States

```tsx
{/* Default */}
<IconButton aria-label='Favorite'><Heart weight='bold' /></IconButton>
{/* Disabled */}
<IconButton aria-label='Favorite' disabled><Heart weight='bold' /></IconButton>
```

### Href

Pass `href` to render the icon button as a routed anchor. Roadie picks the right element + safety attributes based on the href shape: internal routes go through your configured [`RoadieLinkProvider`](https://ticketsolutionsptyltd.github.io/roadie/foundations/linking.md); external (`https://…`) auto-render with `target='_blank' rel='noopener noreferrer'`; protocol hrefs (`mailto:` / `tel:` / `sms:`) render plain anchors.

```tsx
{/* Internal */}
<IconButton aria-label='View cart' href='/cart' emphasis='strong' intent='accent'>
  <ShoppingCart weight='bold' />
</IconButton>
{/* External */}
<IconButton aria-label='Open Oztix in a new tab' href='https://oztix.com.au' emphasis='normal'>
  <ArrowSquareOut weight='bold' />
</IconButton>
{/* Email */}
<IconButton aria-label='Email support' href='mailto:hello@oztix.com.au' emphasis='subtle'>
  <Envelope weight='bold' />
</IconButton>
{/* Phone */}
<IconButton aria-label='Call support' href='tel:+61400000000' emphasis='subtle'>
  <Phone weight='bold' />
</IconButton>
```

Hover each button. The browser status bar shows the resolved `href` exactly as Roadie rendered it (note the `mailto:` / `tel:` URLs vs the routed paths). See [Linking](https://ticketsolutionsptyltd.github.io/roadie/foundations/linking.md) for the full reference, including `external` / `target` / `rel` overrides.

### Download

Add `download` to an `href` icon button to save the file instead of opening it, optionally under a filename.

```tsx
<IconButton aria-label='Download spec' href='/spec.pdf' download='spec.pdf'>
  <Download weight='bold' />
</IconButton>
```

### Escape hatch

For full element control, or anchor props `href` doesn't cover (`hreflang`, `referrerPolicy`), use Base UI's `render` prop. It keeps Base UI's button semantics, `role='button'` and `Space` activation, so the element you pass is yours to make accessible. Passing both `href` and `render` triggers a one-shot dev warning. `render` wins, and provider routing is silently disabled.

## Guidelines

- Always provide an `aria-label` to describe the action since there is no visible text. [Interface copy](https://ticketsolutionsptyltd.github.io/roadie/content/interface-copy.md#buttons) says how to word it.
- For icon links that navigate, prefer `<IconButton href={…}>` and wire [`RoadieLinkProvider`](https://ticketsolutionsptyltd.github.io/roadie/foundations/linking.md) at the app root. The legacy [LinkIconButton](https://ticketsolutionsptyltd.github.io/roadie/components/link-icon-button.md) component is `@deprecated` and will be removed in v3.0.0.

## API reference

### IconButton

```tsx
import { IconButton } from '@oztix/roadie-components/button'
```

- `intent`: `"neutral" | "brand" | "brand-secondary" | "brand-blue" | "brand-orange" | "brand-purple" | "accent" | "danger" | "success" | "warning" | "info" | null`.
- `emphasis`: `"strong" | "normal" | "subtle" | "subtler" | null`.
- `className`: `string | ((state: ButtonState) => string)`. CSS class applied to the element, or a function that returns a class based on the component's state.
- `rel`: `string`. Override the auto `rel='noopener noreferrer'` default on external hrefs.
- `focusableWhenDisabled`: `boolean`. Defaults to `false`. Whether the button should be focusable when disabled.
- `nativeButton`: `boolean`. Defaults to `true`. Whether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`).
- `href`: `string`. Pass a URL to render the button as a routed anchor instead of a `<button>`. Internal hrefs route through the configured `RoadieLinkProvider` (or fall back to plain `<a>`); external hrefs (`http(s)://`, `//…`) render as `<a target='_blank' rel='noopener noreferrer'>`; `mailto:` / `tel:` / `sms:` render as plain `<a>`. Pair with `external`, `target`, or `rel` to override the defaults.
- `external`: `boolean`. Force external-link treatment regardless of `href` shape. Useful for first-party URLs that should still open in a new tab, or for an `https://` URL that should route internally through the provider.
- `target`: `string`. Override the auto `target='_blank'` default on external hrefs.
- `download`: `string | boolean`. Download the `href` instead of navigating, optionally as this filename.
- `aria-label`: `string`. Required.
- `size`: `"xs" | "sm" | "md" | "lg" | "icon-xs" | "icon-sm" | "icon-md" | "icon-lg"`. Defaults to `md`. Icon-button sizing. Use `'xs' | 'sm' | 'md' | 'lg'` (plain) — the `'icon-*'` aliases are accepted for backwards compatibility but discouraged.

Docs updated 19 Oct 2026.
