---
title: Button
description: Buttons trigger actions in the interface. Choose a variant that reflects importance.
---

import { meta as ButtonMeta, Default, Destructive, Ghost, Link } from '@/content/stories/button';
import Canvas from '@/components/component-docs/canvas';
import Demo from '@/components/component-docs/demo';

export const config = {
	dependencies: {
		components: [Canvas, Demo],
		scripts: [
			'../../../components/component-docs/demo.script.tsx',
			'../../../components/component-docs/canvas.script.tsx',
			'../../../components/component-docs/controls.script.tsx',
		],
	},
};


# Button

<p class="docs-lede">Buttons trigger actions in the interface. Choose a variant that reflects importance, and write labels that describe the outcome, not the control type.</p>

## Try it

<Demo of={Default} meta={ButtonMeta} />

## Usage

Import `RuiButton` from the focused module. The default `filled` variant suits the primary action in a small context.

```tsx
import { RuiButton } from '@ecopages/radiant-ui/button';

<RuiButton variant="filled" size="md">
	Save changes
</RuiButton>
```

## Choose a tone

Use `filled` for the main action, `outline` for a supporting action, and `ghost` when the action should recede until needed. `link` looks like inline navigation copy and underlines on hover and focus; pair it with `size="none"` in running text. `destructive` is for irreversible operations.

Pass `href` when the control should navigate; `variant` only changes appearance.

Use the `square` modifier for an icon-only action. It keeps the selected control size on both axes; provide an `aria-label` because it has no visible text.

```tsx
<RuiButton size="sm" square aria-label="Add item">
	<AddIcon />
</RuiButton>
```

<Canvas of={Destructive} meta={ButtonMeta} />
<br />
<Canvas of={Ghost} meta={ButtonMeta} />
<br />
<Canvas of={Link} meta={ButtonMeta} />

## Theming

Button variants map to **semantic action roles** from the active theme, not Tailwind palette steps (`blue-500`, `red-500`, …):

| `variant` | Semantic role | Typical utilities on `.rui-button--*` |
| --- | --- | --- |
| `filled` | `primary` | `bg-primary`, `text-on-primary` |
| `outline` | surface + `border` | `bg-transparent`, `text-on-background`, `border-border` |
| `ghost` | surface-container | `bg-transparent`, `text-on-surface`, hover `bg-surface-container-low` |
| `destructive` | `error` (destructive) | `bg-error`, `text-on-error` |
| `link` | `link` | `text-link`, underline on hover/focus |

Pressed (toggle) states keep the same role family with a stronger fill (`bg-primary-dark`, `bg-error-dark`) and a focus ring.
The active theme role map assigns these semantic roles from its palette for light and dark mode.

To change these colors, update the active theme's semantic role mapping rather than component CSS. `destructive` reuses the `error` status family, matching alerts and toasts.

## Accessibility

- Write labels that describe the resulting action, such as "Save changes".
- Keep disabled buttons explainable with nearby supporting text when needed.
- Do not use color as the only way to communicate action priority or status.
- Set `aria-label` when a button has no visible text (icon-only buttons).
- Toggle buttons set `aria-pressed` when `toggle` / `pressed` are used.

## API

`RuiButton` is a presentational JSX view over a native `<button>` or `<a>`, no custom element. `RuiButtonProps` is a union: pass `href` to render a link, otherwise a button.

### Shared props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `filled` · `outline` · `destructive` · `ghost` · `link` | `filled` | Visual tone. |
| `size` | `none` · `sm` · `md` · `lg` | `md` | Control size; `none` for inline `link` chrome. |
| `square` | `boolean` | `false` | Makes the selected control size square for icon-only actions. |
| `aria-label` | `string` | | Accessible name when there is no visible text. |

### Button branch props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `type` | `button` · `submit` · `reset` | `button` | Native button type. |
| `disabled` | `boolean` | `false` | Disabled state. |
| `pressed` | `boolean` | | Controlled `aria-pressed` (toggle buttons). |
| `defaultPressed` | `boolean` | `false` | Initial pressed state for uncontrolled toggles. |
| `toggle` | `boolean` | `false` | Toggle `aria-pressed` on click when `pressed` is uncontrolled. |
| `name` | `string` | | Native submit name; sent with `value` when the button submits a form. |
| `value` | `string` | | Native submit value. |

### Link branch props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `href` | `string` | | Destination; renders `<a>` instead of `<button>`. |
| `target` | `_self` · `_blank` · `_parent` · `_top` | | Link target. |
| `rel` | `string` | | Link relationship. |
| `download` | `boolean \| string` | | Native download attribute. |
| `aria-current` | `string` | | Current-page indicator for nav links. |

### CSS classes

Public BEM classes on the rendered element (documented via `@cssclass` on `RuiButton`):

| Class | Description |
| --- | --- |
| `.rui-button` | Button / button-styled link root. |
| `.rui-button--filled` | Primary filled action. |
| `.rui-button--outline` | Bordered secondary action. |
| `.rui-button--destructive` | Destructive action. |
| `.rui-button--ghost` | Subtle, borderless action. |
| `.rui-button--link` | Inline link-styled action. |
| `.rui-button--none` | No fixed size (inline `link` chrome). |
| `.rui-button--sm` · `.rui-button--md` · `.rui-button--lg` | Control sizes. |
| `.rui-button--square` | Square control for icon-only actions. |

### Theme roles (per `variant`)

| Variant | CSS variables consumed |
| --- | --- |
| `filled` | `--primary`, `--on-primary`, `--primary-dark` |
| `outline` | `--on-background`, `--border`, `--surface` |
| `ghost` | `--on-surface`, `--surface-container-low`, `--surface-container` |
| `destructive` | `--error`, `--on-error`, `--error-dark` |
| `link` | `--link` |
