# Button

> The action control: primary gradient, glass secondary, ghost, and two danger forms, in three sizes.

Category: Actions. Docs page: https://rime.mjsons.net/components/button

## Installation

```bash
npx shadcn@latest add KezLahd/rime/button
```

Button is every pressable action that is not an icon on its own. Primary carries the brand gesture (the brand gradient with a soft glow) and appears at most once per view. Secondary is glass on the page and a white tile with a brand ring inside solid surfaces (a Card, a Modal, an auth card), switched automatically by the nearest data-surface="solid" ancestor. Ghost is for low-emphasis actions in rows and toolbars.

Danger is the irreversible action, in the danger gradient. dangerSecondary is its inverse (danger ink on the secondary's glass) for destructive steps that are not the last one, such as "Review deletion" before the final "Delete project".

Pass href to render a Next Link with the same look. A disabled or loading link drops its href and leaves the tab order, as a disabled button would. Loading shows a spinner, sets aria-busy, blocks presses and can swap the label (loadingLabel).

## Usage

```tsx
import { Button } from "@/components/ui";

<Button onClick={save}>Save changes</Button>
```

## Props: Button

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `variant` | `ButtonVariant` | `"primary"` | primary = the one brand gradient per view. secondary = glass (white inside solid surfaces). |
| `size` | `ButtonSize` | `"md"` |  |
| `loading` | `boolean` |  | Shows a spinner, sets aria-busy and blocks further presses. |
| `loadingLabel` | `string` |  | Replaces the label while loading, e.g. "Submitting…". |
| `iconStart` | `ReactNode` |  |  |
| `iconEnd` | `ReactNode` |  |  |
| `fullWidth` | `boolean` |  |  |
| `className` | `string` |  |  |
| `children` | `ReactNode` |  |  |
| `href` | `undefined \| string` |  | Renders a Next <Link>. Internal routes only. |
| `asChild` | `boolean \| undefined` |  | Render the single child element (a Next <Link>, an <a>) with the button's look, as shadcn's asChild. The child supplies its own content. |
| `disabled` | `boolean` |  |  |
| `prefetch` | `boolean` |  |  |
| `replace` | `boolean` |  |  |
| `scroll` | `boolean` |  |  |

Also accepts the native attributes of: AnchorHTMLAttributes, AriaAttributes, ButtonHTMLAttributes, DOMAttributes, HTMLAttributes.

## Examples

### Variants

One primary per view. Secondary switches to a white tile inside solid surfaces.

```tsx
<Button>Create invoice</Button>
<Button variant="secondary">Save draft</Button>
<Button variant="ghost">Cancel</Button>
<Button variant="dangerSecondary">Review deletion</Button>
<Button variant="danger">Delete project</Button>
```

### Sizes

sm for table rows and toolbars, md everywhere else, lg for the one action on an auth card.

```tsx
<Button size="sm">Small</Button>
<Button>Medium</Button>
<Button size="lg">Large</Button>
```

### With icons

```tsx
<Button iconStart={<IconPlus size={15} />}>Add team member</Button>
<Button variant="secondary" iconEnd={<IconArrowRight size={15} />}>Continue</Button>
```

### Loading and disabled

Loading keeps the fill, dulls it and swaps the label. Disabled is a flat tint, not a faded gradient.

```tsx
<Button loading loadingLabel="Saving…">Save</Button>
<Button disabled>Submit</Button>
<Button href="/projects" variant="secondary">Link as button</Button>
```

### shadcn names

shadcn's variant and size names work as aliases: default is primary, destructive is danger, outline is secondary; link is an inline text link; icon, icon-sm and icon-lg are square. buttonVariants is buttonClassName.

```tsx
<Button variant="default">Save</Button>
<Button variant="outline">Cancel</Button>
<Button variant="destructive">Delete</Button>
<Button variant="link">Read the terms</Button>
<Button variant="outline" size="icon" aria-label="Settings"><Settings size={16} /></Button>
```

### asChild

asChild renders the single child (a Next Link, an <a>) with the button's look, as shadcn does. href remains the shorter way for internal links.

```tsx
import Link from "next/link";

<Button asChild>
  <Link href="/projects/new">New project</Link>
</Button>
<Button variant="destructive" size="icon" asChild>
  <Link href="/trash" aria-label="Open trash"><Trash2 size={16} /></Link>
</Button>
```

## Theme tokens

Per-component tokens. Undeclared by default: the default below is the fallback the CSS uses. Set any of them at :root or on a wrapper to restyle every instance.

| Token | Default |
| --- | --- |
| `--button-case` | `none` |
| `--button-danger-bg` | `var(--danger-gradient)` |
| `--button-dur` | `var(--dur)` |
| `--button-ease` | `var(--ease)` |
| `--button-gap` | `8px` |
| `--button-ghost-ink` | `var(--ink-brand)` |
| `--button-height-lg` | `var(--control-h-lg)` |
| `--button-height-md` | `var(--control-h-md)` |
| `--button-height-sm` | `var(--control-h-sm)` |
| `--button-hover-glow` | `var(--glow-md-hover)` |
| `--button-hover-overlay` | `rgba(var(--rgb-white), 0.07)` |
| `--button-lift` | `0px` |
| `--button-link-ink` | `var(--ink-brand)` |
| `--button-outline` | `var(--shadow-control)` |
| `--button-outline-active` | `inset 0 0 0 1px rgba(var(--rgb-brand), 0.32)` |
| `--button-outline-hover` | `0 3px 8px rgba(var(--rgb-brand-deep), 0.1), inset 0 0 0 1px rgba(var(--rgb-brand), 0.32)` |
| `--button-pad-lg` | `calc(24px * var(--density))` |
| `--button-pad-md` | `calc(18px * var(--density))` |
| `--button-pad-sm` | `calc(12px * var(--density))` |
| `--button-press-overlay` | `rgba(var(--rgb-shade), 0.14)` |
| `--button-primary-bg` | `var(--brand-gradient)` |
| `--button-primary-ink` | `var(--ink-inverse)` |
| `--button-radius` | `var(--r-sm)` / `var(--r-md)` / `var(--r-lg)` |
| `--button-secondary-bg` | `rgba(var(--rgb-white), 0.45)` |
| `--button-secondary-hover-bg` | `rgba(var(--rgb-white), 0.68)` |
| `--button-secondary-hover-shadow` | `inset 0 0 0 1px rgba(var(--rgb-white), 0.9), var(--shadow-specular), 0 4px 12px rgba(var(--rgb-brand-deep), 0.1)` |
| `--button-secondary-ink` | `var(--ink-heading)` |
| `--button-size-lg` | `15px` |
| `--button-size-md` | `14px` |
| `--button-size-sm` | `12.5px` |
| `--button-tracking` | `-0.005em` |
| `--button-trim` | `0em` |
| `--button-weight` | `700` |

System tokens it reads: `--blur-callout`, `--brand-deep`, `--brand-gradient`, `--control-h-lg`, `--control-h-md`, `--control-h-sm`, `--danger-fill`, `--danger-gradient`, `--danger-ink`, `--danger-line`, `--density`, `--dur`, `--ease`, `--focus-gap`, `--focus-ring`, `--font-body`, `--glass-edge`, `--glass-edge-strong`, `--glow-danger`, `--glow-danger-hover`, `--glow-md`, `--glow-md-hover`, `--glow-sm`, `--ink-brand`, `--ink-disabled`, `--ink-heading`, `--ink-inverse`, `--ink-muted`, `--r-2xs`, `--r-lg`, `--r-md`, `--r-sm`, `--rgb-brand`, `--rgb-brand-deep`, `--rgb-contact`, `--rgb-danger`, `--rgb-shade`, `--rgb-white`, `--shadow-control`, `--shadow-specular`, `--shadow-specular-dark`, `--surface-solid`, `--wash-active`, `--wash-hover`, `--wash-subtle`.

## Accessibility

- Renders a real <button type="button"> (or a Next <Link>), so Enter and Space work and the role is right with no ARIA.
- Focus is a 2px ring with a white gap (--focus-ring), drawn with box-shadow so it follows the radius.
- Loading sets aria-busy and disables the button; keep the loadingLabel short and present tense ("Saving…").
- A link with disabled renders a non-focusable span with role="link" and aria-disabled, never a dead href.
- Square icon sizes have no text: give them aria-label (or use IconButton, which requires a label).
- Every button carries data-slot="button", data-variant and data-size, for styling from a CSS Module without new props.
- Primary text must hold 4.5:1 on the gradient's light stop; re-check it in the Studio's Contrast tab when a theme changes the gradient.

## Do

- Use one primary per view: the action the screen exists for.
- Write labels as verbs plus the object: "Add team member", "Send question".
- Use dangerSecondary for the step that leads to a destructive confirm, and danger only on the confirm itself.

## Don't

- Never scale a button on hover; hover deepens the glow (no lift: --button-lift is 0 in the default theme).
- Don't put two primary buttons side by side; demote one to secondary.
- Don't fade a disabled gradient; the flat tint is the disabled state.
- Don't use Button for navigation inside text; use a plain link.

Related: https://rime.mjsons.net/components/icon-button, https://rime.mjsons.net/components/modal
