# Button

> An interactive control that triggers an action when pressed

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

An interactive control that triggers an action when pressed.

## Import

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

## Examples

### Default

```tsx
<Button>Button</Button>
```

### Emphasis

```tsx
<Button emphasis='strong'>Strong</Button>
<Button emphasis='normal'>Normal</Button>
<Button emphasis='subtle'>Subtle</Button>
<Button emphasis='subtler'>Subtler</Button>
```

### Sizes

```tsx
<Button size='xs'>Extra small</Button>
<Button size='sm'>Small</Button>
<Button size='md'>Medium</Button>
<Button size='lg'>Large</Button>
```

### Full width

A button is as wide as its label and takes its parent's alignment. Add `w-full` to fill the space. A parent's `items-stretch` or `self-stretch` won't widen it. To go back to the label's width at a breakpoint, use `w-fit`, not `w-auto`.

```tsx
<div className='grid max-w-sm gap-2'>
  <Button emphasis='strong' className='w-full'>Checkout</Button>
  <Button className='w-full sm:w-fit'>Keep browsing</Button>
</div>
```

### Intents

Neutral is the default intent. Each intent communicates a different purpose: accent for primary actions, danger for destructive actions, and so on. Hover and focus states adapt automatically.

```tsx
{/* Strong */}
<Button emphasis='strong' intent='neutral'>Neutral</Button>
<Button emphasis='strong' intent='accent'>Accent</Button>
<Button emphasis='strong' intent='brand'>Brand</Button>
<Button emphasis='strong' intent='info'>Info</Button>
<Button emphasis='strong' intent='success'>Success</Button>
<Button emphasis='strong' intent='warning'>Warning</Button>
<Button emphasis='strong' intent='danger'>Danger</Button>
{/* Normal */}
<Button emphasis='normal' intent='neutral'>Neutral</Button>
<Button emphasis='normal' intent='accent'>Accent</Button>
<Button emphasis='normal' intent='brand'>Brand</Button>
<Button emphasis='normal' intent='info'>Info</Button>
<Button emphasis='normal' intent='success'>Success</Button>
<Button emphasis='normal' intent='warning'>Warning</Button>
<Button emphasis='normal' intent='danger'>Danger</Button>
{/* Subtle */}
<Button emphasis='subtle' intent='neutral'>Neutral</Button>
<Button emphasis='subtle' intent='accent'>Accent</Button>
<Button emphasis='subtle' intent='brand'>Brand</Button>
<Button emphasis='subtle' intent='info'>Info</Button>
<Button emphasis='subtle' intent='success'>Success</Button>
<Button emphasis='subtle' intent='warning'>Warning</Button>
<Button emphasis='subtle' intent='danger'>Danger</Button>
{/* Subtler */}
<Button emphasis='subtler' intent='neutral'>Neutral</Button>
<Button emphasis='subtler' intent='accent'>Accent</Button>
<Button emphasis='subtler' intent='brand'>Brand</Button>
<Button emphasis='subtler' intent='info'>Info</Button>
<Button emphasis='subtler' intent='success'>Success</Button>
<Button emphasis='subtler' intent='warning'>Warning</Button>
<Button emphasis='subtler' intent='danger'>Danger</Button>
```

### States

Hover and focus states use the intent color scale automatically via `is-interactive`.

```tsx
{/* Default */}
<Button>Button</Button>
{/* Disabled */}
<Button disabled>Button</Button>
```

### Href

Pass `href` to render the 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 hrefs (`https://…`, `//…`) auto-render with `target='_blank' rel='noopener noreferrer'`; protocol hrefs (`mailto:` / `tel:` / `sms:`) render plain anchors.

```tsx
{/* Internal */}
<Button href='/foundations/linking' emphasis='strong' intent='accent'>
  Read about linking
</Button>
{/* External */}
<Button href='https://oztix.com.au' emphasis='normal'>
  Open Oztix
  <ArrowSquareOut weight='bold' className='size-4' />
</Button>
{/* Protocol */}
<Button href='mailto:hello@oztix.com.au' emphasis='subtle'>
  <Envelope weight='bold' className='size-4' />
  Email us
</Button>
```

Hover each button. The browser status bar shows the resolved `href` exactly as Roadie rendered it. See [Linking](https://ticketsolutionsptyltd.github.io/roadie/foundations/linking.md) for the full reference: provider setup, the external/internal/protocol decision tree, and the `external`/`target`/`rel` overrides.

### Download

Add `download` to an `href` button to save the file instead of opening it, optionally under a filename. A file isn't a route, so the button renders a plain `<a>` and skips the provider's Link, which would otherwise prefetch the file as a page. That also means it gets no base path: if your app has one, add it to the `href` yourself.

```tsx
<Button href='/roadie-logo.png' download='roadie-logo.png'>
  <DownloadIcon weight='bold' className='size-4' />
  Download logo
</Button>
```

### 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, which are `role='button'` and `Space` activation. 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

Write labels with [Interface copy](https://ticketsolutionsptyltd.github.io/roadie/content/interface-copy.md#buttons).

## Accessibility

- **Keyboard**: `Enter` or `Space` activates the button. With `href` it is a link, so `Enter` follows it and `Space` scrolls the page. Focus is indicated by a semi-transparent ring via `is-interactive`.
- **ARIA**: Renders a native `<button>`, or an `<a>` with `href`. A disabled link gets `aria-disabled`, leaves the tab order and doesn't navigate.
- **IconButton**: Requires an `aria-label` prop (enforced by TypeScript) since there is no visible text.

## API reference

### Button

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

- `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>`).
- `rel`: `string`. Override the auto `rel='noopener noreferrer'` default on external hrefs.
- `className`: `string | ((state: ButtonState) => string)`. CSS class applied to the element, or a function that returns a class based on the component's state.
- `intent`: `"neutral" | "brand" | "brand-secondary" | "brand-blue" | "brand-orange" | "brand-purple" | "accent" | "danger" | "success" | "warning" | "info" | null`.
- `emphasis`: `"strong" | "normal" | "subtle" | "subtler" | null`.
- `size`: `"xs" | "sm" | "md" | "lg" | "icon-xs" | "icon-sm" | "icon-md" | "icon-lg" | null`.
- `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.

Docs updated 19 Oct 2026.
