# Button (/work/petavue-design-system/docs/components/button)

```jsx
import { Button } from "@/ui";
```

## Variants [#variants]

Pick the variant by how important the action is on that screen, not by colour.


```jsx
<Button variant="primary" label="Publish" />
<Button variant="secondary" label="Save as template" />
<Button variant="secondaryGhost" label="Cancel" />
<Button variant="ghost" label="Skip" />
<Button variant="blueGhost" label="View all" />
```

| Variant | Use for |
|---|---|
| `primary` | The one main action in a header, dialog or form. Publish, Save, Send. |
| `secondary` | A second action next to a primary one, or the main action in a quiet area. |
| `secondaryGhost` | Neutral actions with a border: Cancel, Close, filters and menu triggers. |
| `ghost` | Low-emphasis actions in toolbars and rows: Skip, Reject, View all. |
| `blueGhost` | A text-like action that should still read as a link. |

## Status variants [#status-variants]

Use these only when the action itself is destructive or confirms something positive.


```jsx
<Button variant="red" label="Delete page" icon={Trash} />
<Button variant="green" label="Approve" />
```

## Sizes [#sizes]

The four sizes match Figma's Small, Medium, Large and XL. `md` is the default and fits almost everywhere. Use `sm` in dense rows and table cells, `lg` for the main action in a dialog footer, and `xl` only for a page's single main call to action.


```jsx
<Button size="sm" label="Small" />
<Button size="md" label="Medium" />
<Button size="lg" label="Large" />
<Button size="xl" label="Extra large" />
```

| Size | Figma | Height | Padding | Gap | Text | Icon | Icon-only |
|---|---|---|---|---|---|---|---|
| `sm` | Small | 28px | 6px 12px | 6px | 12px regular | 12px | 28 × 28px |
| `md` | Medium | 32px | 6px 12px | 6px | 12px regular | 12px | 32 × 32px |
| `lg` | Large | 36px | 6px 16px | 8px | 14px medium | 16px | 40 × 36px |
| `xl` | XL | 42px | 10px 24px | 12px | 14px medium | 16px | 48 × 42px |

All sizes have an 8px radius.

<Callout>

Figma sets Small's label at 10px. Code uses 12px, because 12px is the smallest text in the product. Figma's Large and XL icon-only buttons are wider than they are tall (40 × 36 and 48 × 42); code follows Figma.

</Callout>

## With an icon [#with-an-icon]

Pass a Phosphor icon component, not an element. Icons sit before the label by default; use `iconPosition="suffix"` for arrows that point to where the action goes. An icon-only button must have an `aria-label`.


```jsx
import { ArrowRight, Plus, Trash } from "@phosphor-icons/react";

<Button variant="primary" label="New page" icon={Plus} />
<Button variant="ghost" label="View all 11" icon={ArrowRight} iconPosition="suffix" />
<Button variant="secondaryGhost" icon={Trash} aria-label="Delete" />
```

Button sizes the icon for you: 12px on `sm` and `md`, 16px on `lg` and `xl`.


## States [#states]

Each variant has four states in Figma: Default, Hover, Pressed and Disabled. In code, Hover is `:hover`, Pressed is `:active`, and Disabled is the `disabled` prop. Hover over and press the buttons above to see them.

| Variant | Default | Hover | Pressed | Disabled |
|---|---|---|---|---|
| `primary` | Primary 500 fill, white text | Primary 800 fill | Primary 300 fill | Grey 100 fill, Grey 300 text |
| `secondary` | White fill, Primary 500 border and text | Primary 100 fill | Primary 50 fill, Primary 100 border, Primary 300 text | Grey 100 fill, Grey 300 text |
| `ghost` | No fill, Grey 600 text | Grey 50 fill, Grey 800 text | No fill, Grey 300 text | No fill, Grey 300 text |
| `blueGhost` | No fill, Primary 500 text | Primary 50 fill, Primary 800 text | No fill, Primary 300 text | No fill, Primary 300 text |

`secondaryGhost`, `red` and `green` are code-only variants with no Figma component yet.

## Disabled [#disabled]

Disable a button when its action cannot run yet, and say why nearby or in a tooltip. Do not hide the button: people should see the action exists.


```jsx
<Button variant="primary" label="Publish" disabled />
```

## Props [#props]

| Prop | Type | Default | Notes |
|---|---|---|---|
| `variant` | `"primary" \| "secondary" \| "secondaryGhost" \| "ghost" \| "blueGhost" \| "red" \| "green"` | `"primary"` | |
| `size` | `"sm" \| "md" \| "lg" \| "xl"` | `"md"` | |
| `label` | `string` | | The text. `children` also works when you need markup. |
| `icon` | Phosphor icon component | | Rendered at 12px (`sm`, `md`) or 16px (`lg`, `xl`). |
| `iconPosition` | `"prefix" \| "suffix"` | `"prefix"` | |
| `iconWeight` | `"regular" \| "bold" \| "fill" \| …` | `"regular"` | |
| `disabled` | `boolean` | `false` | |
| `type` | `"button" \| "submit" \| "reset"` | `"button"` | Set `"submit"` inside forms. |
| `className` | `string` | | For layout only (width, margin). Do not restyle the button. |

Any other prop (`aria-*`, `data-*`, `onClick`) is passed to the `<button>`. The ref is forwarded, so a Button can be a menu or tooltip trigger.

## Accessibility [#accessibility]

- It renders a native `<button>`, so keyboard and screen reader support come for free.
- The focus ring uses `--shadow-focus`. Do not remove it.
- Icon-only buttons need `aria-label`. When you wrap one in a Tooltip, pass `describeChild` so the tooltip does not replace that label.

## Do and don't [#do-and-dont]

- Do write labels as a verb and an object: "Publish page", "Add to dashboard".
- Do keep one `primary` per area.
- Don't make a lookalike with a `<div>` or a styled `<a>`. If you need a link that looks like a button, use a Button with `onClick` that navigates.
- Don't override colours with `className`. If no variant fits, the design needs a decision, not a new colour.
