# Button

A versatile action component that triggers commands and navigates users.

## When to Use

- Trigger an immediate in-page action (e.g., open a dialog)
- Navigate to another route or URL using the `to` prop while keeping button styling
- Toggle a boolean state with `isSelected`
- Display progress feedback with `isLoading`

## Component

---

## Properties

- **`type`** `'primary' | 'outline' | 'outline-2' | 'clear' | 'link'` (default: `outline`) — Visual style variant of the button (`outline-2` is identical to `outline` but uses `#surface-3` as the base fill; not available for the `special` theme)
- **`theme`** `'default' | 'danger' | 'success' | 'warning' | 'note' | 'special'` (default: `default`) — Semantic colour palette theme
- **`size`** `'xsmall' | 'small' | 'medium' | 'large' | 'xlarge' | 'inline' | number` (default: `medium`) — Button size. Use `'inline'` for link-type buttons or a number/string for custom sizes (e.g. `64` or `'8x'`)
- **`icon`** — Icon rendered before the content. Can be: ReactNode, `true` (empty slot), or function `({ loading, selected, ...mods }) => ReactNode | true`
- **`rightIcon`** — Icon rendered after the content. Can be: ReactNode, `true` (empty slot), or function `({ loading, selected, ...mods }) => ReactNode | true`
- **`children`** `ReactNode` — Button label or custom content
- **`label`** `string` — Accessible label for the button (used as `aria-label`)
- **`tooltip`** `string | boolean | object` — Tooltip content. Use a string for simple text, `true` for auto tooltip on overflow, or an object with `{ title, auto, placement, ...tooltipProps }`
- **`defaultTooltipPlacement`** `Placement` (default: `top`) — Default placement for the button's tooltip
- **`isLoading`** `boolean` (default: `false`) — Show loading spinner and disable interactions
- **`isSelected`** `boolean` (default: `false`) — Marks the button as pressed / selected (toggle)
- **`to`** `string` — Destination URL or route; prefix with `!` to open in new tab, `@` to bypass router
- **`htmlType`** `'button' | 'submit' | 'reset'` — HTML button type attribute
- **`onPress`** `function` — Callback fired when the button is activated by mouse, touch, or keyboard

### Base Properties

Supports [Base properties](../../BaseProperties.md)

### Styling Properties

#### styles

Customises the root element of the component.

**Sub-elements:**

- `Icon` – wrapper around the leading icon
- `RightIcon` – wrapper around the trailing icon
- `Label` – the button text content

### Style Properties

These properties allow direct style application without using the `styles` prop:

- **Base:** `display`, `font`, `preset`, `hide`, `whiteSpace`, `opacity`, `transition`
- **Position:** `gridArea`, `order`, `gridColumn`, `gridRow`, `placeSelf`, `alignSelf`, `justifySelf`, `zIndex`, `margin`, `inset`, `position`
- **Dimension:** `width`, `height`, `flexBasis`, `flexGrow`, `flexShrink`, `flex`
- **Block:** `border`, `radius`, `shadow`, `outline`, `padding`, `paddingInline`, `paddingBlock`, `overflow`, `scrollbar`, `textAlign`
- **Color:** `color`, `fill`, `fade`, `image`
- **Text:** `textTransform`, `fontWeight`, `fontStyle`
- **Flow:** `flow`, `placeItems`, `placeContent`, `alignItems`, `alignContent`, `justifyItems`, `justifyContent`, `align`, `justify`, `gap`, `columnGap`, `rowGap`, `gridColumns`, `gridRows`, `gridTemplate`, `gridAreas`

### Modifiers

The `mods` prop accepts the following modifiers you can override:

- **`hovered`** `boolean` — Forces hover state
- **`pressed`** `boolean` — Forces pressed state
- **`focused`** `boolean` — Forces focus ring
- **`disabled`** `boolean` — Forces disabled appearance
- **`loading`** `boolean` — Displays loading spinner
- **`selected`** `boolean` — Displays selected state
- **`has-icons`** `boolean` — Indicates that the button contains at least one icon
- **`has-icon`** `boolean` — Indicates that the button has a leading icon
- **`has-right-icon`** `boolean` — Indicates that the button has a trailing icon
- **`single-icon`** `boolean` — Icon-only button without text
- **`text-only`** `boolean` — Button has only text content, no icons

## Variants

### Types. `type` prop

- `primary` – Emphasised call-to-action.
- `outline` – Border with transparent background; `isSelected` adds brand-tinted fill.
- `clear` – No border, transparent background; `isSelected` adds brand-tinted fill.
- `link` – Styled as a textual link.

### Themes. `theme` prop

- `default` – Brand purple colours.
- `danger` – Red palette for destructive actions.
- `success` – Green palette for positive actions.
- `warning` – Yellow/amber palette for caution or attention.
- `note` – Violet palette for informational context.
- `special` – White palette for dark backgrounds.

### Sizes. `size` prop

- `xsmall` – Extra compact height (24px).
- `small` – Compact height (28px).
- `medium` – Default height (32px).
- `large` – Spacious height (40px).
- `xlarge` – Extra spacious height (48px).
- `inline` – Inline size for link-type buttons (no fixed height).
- Custom values – Pass a number (pixels) or a string (e.g. `'8x'`) for custom sizing.

## Examples

### Basic Usage

```jsx
<Button onPress={() => console.log('Pressed')}>Save</Button>
```

### Icon Button

```jsx
import { IconPlus } from '@tabler/icons-react';

{
  /* Standard icon */
}
<Button icon={<IconPlus />} aria-label="Add item" />;

{
  /* Empty slot (reserves space but shows nothing) */
}
<Button icon={true} aria-label="Empty slot" />;

{
  /* Dynamic icon based on mods */
}
<Button
  icon={({ loading }) => (loading ? <SpinnerIcon /> : <IconPlus />)}
  aria-label="Dynamic icon"
>
  Save
</Button>;

{
  /* Return true from function for empty slot */
}
<Button
  icon={({ selected }) => (selected ? <CheckIcon /> : true)}
  aria-label="Conditional icon"
>
  Option
</Button>;
```

### Link Button

```jsx
<!-- Navigate to a new page using the router -->
<Button to="/pricing">Pricing</Button>
<!-- Use `!` prefix to open in a new tab -->
<Button to="!/pricing">Pricing</Button>
<!-- Use `@` prefix to navigate to a new page without using the router -->
<Button to="@/pricing">Pricing</Button>
<!-- Use `@` to reload the page -->
<Button to="@/pricing">Pricing</Button>
```

### Loading State

```jsx
<Button isLoading>Processing…</Button>
```

### With Tooltip

```jsx
{
  /* Simple tooltip text */
}
<Button icon={<IconPlus />} tooltip="Add new item" aria-label="Add" />;

{
  /* Auto tooltip on overflow */
}
<Button tooltip={true} width="100px">
  This text will be truncated
</Button>;
```

### Custom Size

```jsx
{
  /* Using a number (pixels) */
}
<Button size={64}>Large Custom</Button>;

{
  /* Using a string multiplier */
}
<Button size="8x">8x Size</Button>;
```

## Accessibility

### Keyboard Navigation

- `Tab` – Moves focus to the button.
- `Space` / `Enter` – Activates the button.

### Screen Reader Support

- Announces role "button".
- Announces state changes such as "pressed", "disabled", or "selected".
- Provide `label`/`aria-label` when the button has no visible text.

### ARIA Properties

- `aria-label`, `aria-labelledby` – Accessible name.
- `aria-pressed` – Managed automatically when `isSelected` is used.
- Additional ARIA attributes supported by React Aria's `useButton`.

## Behaviour Inside Popovers

By default, pressing a `Button` inside an open popover (any overlay that
uses `usePopoverSync` — including the popovers rendered by `FilterPicker`,
`Picker`, `ComboBox`, `Select`, `MenuTrigger`, and
`DialogTrigger type="popover"`) **dismisses that popover** after the user's
`onPress` runs. This matches the common UX where a popover is a transient
surface: any action inside it should close it.

Modal / tray / fullscreen Dialogs are **not** affected — a Button inside a
modal Dialog does not auto-close it.

### Opt-outs

There are three ways to keep the popover open when a Button is pressed:

1. **Automatic for popover triggers.** Buttons that act as popover triggers
   already carry `data-popover-trigger` (applied by `MenuTrigger`,
   `DialogTrigger type="popover"`, `FilterPicker`, `Picker`, `ComboBox`,
   `Select`). Modal-type `DialogTrigger` children are **not** marked, so they
   correctly dismiss the parent popover and let the modal take over.
2. **Manual via `data-popover-keep`.** Add the attribute to the button
   (toggle case) or to any ancestor element (subtree case — useful when a
   custom inline editor inside a popover footer should not collapse on
   every interaction):

   ```jsx
   <Button data-popover-keep onPress={() => setMode((m) => !m)}>
     Toggle
   </Button>

   <div data-popover-keep>
     {/* All Buttons inside this wrapper opt out. */}
   </div>
   ```

3. **Custom non-Cube triggers.** Call `useDismissParentPopover()` yourself if
   your control should dismiss the popover but does not extend `Button` /
   `ItemButton`:
   ```jsx
   const dismiss = useDismissParentPopover();
   <MyCustomPressable
     onPress={(e) => {
       doThing();
       dismiss(e.currentTarget);
     }}
   />;
   ```

### Async `onPress`

The dismiss is dispatched via the EventBus, which defers with `setTimeout(0)`.
This means synchronous `onPress` handlers (and any React state updates they
trigger) flush **before** the popover closes — including the common case of
opening a hoisted modal via `useDialogContainer` from a popover footer (the
modal mounts first, then the popover closes behind it). If your `onPress` is
async and `await`s before opening another overlay, the popover will close
first and the new overlay will appear after — usually fine, but worth noting.

## Best Practices

1. **Do**: Provide a label for icon-only buttons
   ```jsx
   <Button icon={<IconPlus />} aria-label="Add" />
   ```
2. **Don't**: Use the `link` type for destructive actions – prefer `danger.primary` instead.
3. **Callbacks**: Always use `onPress` for actions that don't navigate. Use `onClick` only if you need to access the event object.
4. **Accessibility**: Use `isLoading` rather than disabling a button without feedback.
5. **Performance**: Reuse icon components to avoid unnecessary re-renders.

## Suggested Improvements

- Provide `block` modifier to stretch the button 100% width.

## Related Components

- [Link](./Link.md) – Semantic navigation styled like a button.
- [ItemButton](./ItemButton.md) – Interactive button built on Item with icon support.
