# Button

> Trigger for user actions; renders as `<button>` or `<a>` depending on context

- Tier: primitives
- Storybook: `Components/Button`
- Import: `import { Button } from '@amezquita/design-system/components/primitives/Button'`

## Props

| Prop | Type | Description |
|---|---|---|
| `variant?` | `'primary' \| 'secondary' \| 'ghost' \| 'link'` |  |
| `children` | `React.ReactNode` |  |
| `onClick?` | `(event: React.MouseEvent<HTMLButtonElement \| HTMLAnchorElement>) => void` |  |
| `disabled?` | `boolean` |  |
| `loading?` | `boolean` |  |
| `fullWidth?` | `boolean` |  |
| `type?` | `'button' \| 'submit' \| 'reset'` |  |
| `icon?` | `React.ReactNode` |  |
| `iconPosition?` | `'start' \| 'end'` |  |
| `arrow?` | `boolean` | Trailing arrow after the label. Off by default — turn it on for a call to action that leads somewhere. Ignored on the `link` variant and while loading. |
| `motion?` | `'functional' \| 'expressive'` | `'expressive'` adds the hover wipe and glow, and loads GSAP on demand. Left at `'functional'`, hover is a plain background change and no animation code is fetched. See decisions/0016. |
| `aria-label?` | `string` |  |
| `href?` | `string` |  |
| `curtainColor?` | `string` |  |
| `onNavigate?` | `(href: string, curtainColor?: string) => void` | Called instead of a plain navigation when set and the link is internal — lets host apps inject route-transition behavior (e.g. a page-curtain animation) without Button depending on any specific router or transition system. Omit for a plain internal navigation. |

Also accepts all props of: `Omit<React.HTMLAttributes<HTMLElement>, 'onClick' | 'type'>`

## Tokens

| Token | Type | Value |
|---|---|---|
| `--button-arrow-nudge` | dimension | `2px` |
| `--button-ghost-background` | color | `transparent` |
| `--button-ghost-border` | color | `transparent` |
| `--button-glow-color` | color | `rgba(255, 255, 255, 0.45)` |
| `--button-glow-size` | dimension | `52px` |
| `--button-outline-border-width` | dimension | `2px` |
| `--button-padding-x` | dimension | `24px` |
| `--button-padding-y` | dimension | `12px` |
| `--button-secondary-background` | color | `transparent` |
| `--button-secondary-background-hover` | color | `#262626` † |
| `--button-secondary-border` | color | `#262626` † |
| `--button-secondary-foreground` | color | `#262626` † |
| `--button-spinner-duration` | duration | `750ms` |
| `--button-wipe-duration-curve` | duration | `220ms` |
| `--button-wipe-duration-curve-out` | duration | `180ms` |
| `--button-wipe-duration-enter` | duration | `550ms` |
| `--button-wipe-duration-exit` | duration | `400ms` |

† resolves differently across base/portfolio and light/dark themes — see `tokens.json` for all four values.

## Accessibility

- Semantic element: `<button>` by default; `<a>` when `href` is passed
- When rendered as `<a>`: no `disabled` attribute — use visual suppression only if truly needed
- ARIA: use `aria-label` for icon-only buttons; `disabled` on `<button>` removes it from tab order
- Keyboard: `Enter` + `Space` activate `<button>`; `Enter` follows `<a>`
