# Button

> Polymorphic, accessible button with four variants, five sizes, loading state, optional dropdown, and href support.

- Available in: `@sisyphos-ui/react`, `@sisyphos-ui/vue`, `@sisyphos-ui/angular`
- Docs: https://sisyphosui.com/docs/components/button
- WAI-ARIA pattern: [Button](https://www.w3.org/WAI/ARIA/apg/patterns/button/)

## Installation

Pick the framework binding that matches your stack:

```bash
pnpm add @sisyphos-ui/react   # React 18+
pnpm add @sisyphos-ui/vue     # Vue 3+
pnpm add @sisyphos-ui/angular # Angular 17+
```

## Import

```tsx
import "@sisyphos-ui/react/styles.css";
import { Button } from "@sisyphos-ui/react";
```

## Framework usage

### React 18+

```tsx
import { Button } from "@sisyphos-ui/react";

export function Save() {
  return (
    <Button variant="contained" color="primary" onClick={() => console.log("save")}>
      Save changes
    </Button>
  );
}
```

### Vue 3+

```vue
<script setup lang="ts">
import { Button } from "@sisyphos-ui/vue";
</script>

<template>
  <Button variant="contained" color="primary" @click="console.log('save')">
    Save changes
  </Button>
</template>
```

### Angular 17+

```ts
import { Component } from "@angular/core";
import { Button } from "@sisyphos-ui/angular";

@Component({
  selector: "app-save",
  standalone: true,
  imports: [Button],
  template: `
    <sui-button variant="contained" color="primary" (buttonClick)="save()">
      Save changes
    </sui-button>
  `,
})
export class SaveComponent {
  save() { console.log("save"); }
}
```

## Examples

### Default

```tsx
import { Button } from "@sisyphos-ui/react";

export function Example() {
  return <Button>Click me</Button>;
}
```

### Variants

Four visual treatments.

```tsx
import { Button } from "@sisyphos-ui/react";

export function Example() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button variant="contained">Contained</Button>
      <Button variant="outlined">Outlined</Button>
      <Button variant="soft">Soft</Button>
      <Button variant="text">Text</Button>
    </div>
  );
}
```

### Semantic colors

Pick a color token to communicate intent.

```tsx
import { Button } from "@sisyphos-ui/react";

export function Example() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button color="primary">Primary</Button>
      <Button color="success">Success</Button>
      <Button color="error">Error</Button>
      <Button color="warning">Warning</Button>
      <Button color="info">Info</Button>
    </div>
  );
}
```

### Sizes

```tsx
import { Button } from "@sisyphos-ui/react";

export function Example() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button size="xs">XS</Button>
      <Button size="sm">SM</Button>
      <Button size="md">MD</Button>
      <Button size="lg">LG</Button>
      <Button size="xl">XL</Button>
    </div>
  );
}
```

### With icons

`startIcon` and `endIcon` accept any ReactNode — drop in a lucide icon or inline SVG.

```tsx
import { Button } from "@sisyphos-ui/react";
import { Plus, ChevronRight, Check } from "lucide-react";

export function Example() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button startIcon={<Plus size={16} />}>New project</Button>
      <Button variant="outlined" endIcon={<ChevronRight size={16} />}>
        Continue
      </Button>
      <Button variant="soft" color="success" startIcon={<Check size={16} />}>
        Saved
      </Button>
    </div>
  );
}
```

### Loading state

`aria-busy` is set and click is suppressed while loading.

```tsx
import { Button } from "@sisyphos-ui/react";

export function Example() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button loading>Saving…</Button>
      <Button loading color="success">Deploying</Button>
      <Button loading variant="outlined" color="error">Deleting</Button>
    </div>
  );
}
```

### Real world — sign-in form

Primary submit with a text-variant secondary action.

```tsx
import { Button, Card, Input } from "@sisyphos-ui/react";

export function SignInForm() {
  return (
    <Card className="w-full max-w-sm">
      <Card.Body>
        <form onSubmit={(e) => e.preventDefault()} className="flex flex-col gap-3">
          <Input label="Email" type="email" placeholder="hey@example.com" />
          <Input label="Password" type="password" placeholder="••••••••" />
          <div className="mt-2 flex items-center justify-between">
            <Button variant="text" size="sm">Forgot?</Button>
            <Button type="submit">Sign in</Button>
          </div>
        </form>
      </Card.Body>
    </Card>
  );
}
```

### Real world — pricing CTA

Full-width primary CTA with an inline compare link.

```tsx
import { Button, Card, Chip } from "@sisyphos-ui/react";

export function PricingCard() {
  return (
    <Card className="w-full max-w-xs">
      <Card.Body>
        <div className="flex flex-col items-start gap-3">
          <Chip variant="soft" color="primary">Team</Chip>
          <div>
            <div className="text-[28px] font-bold">
              $29<span className="text-[14px] font-normal"> / month</span>
            </div>
            <p>Up to 10 seats, unlimited projects.</p>
          </div>
          <Button className="w-full">Start free trial</Button>
          <Button variant="text" size="sm">Compare plans →</Button>
        </div>
      </Card.Body>
    </Card>
  );
}
```

### Real world — editor toolbar

Dense text buttons grouped around a primary publish action.

```tsx
import { Button, Card, Chip } from "@sisyphos-ui/react";

export function EditorToolbar() {
  return (
    <Card>
      <Card.Body className="!py-1.5">
        <div className="inline-flex items-center gap-1">
          <Button variant="text" size="sm">Bold</Button>
          <Button variant="text" size="sm">Italic</Button>
          <Button variant="text" size="sm">Link</Button>
          <span className="mx-1 h-4 w-px bg-[var(--color-border)]" />
          <Chip variant="soft" color="success" size="sm">Draft saved</Chip>
          <span className="mx-1 h-4 w-px bg-[var(--color-border)]" />
          <Button size="sm">Publish</Button>
        </div>
      </Card.Body>
    </Card>
  );
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| variant | `"contained" \| "outlined" \| "text" \| "soft"` | `"contained"` | Visual treatment. |
| color | `"primary" \| "success" \| "error" \| "warning" \| "info"` | `"primary"` | Semantic color token. |
| size | `"xs" \| "sm" \| "md" \| "lg" \| "xl"` | `"md"` | Button size. |
| loading | `boolean` | `false` | Show a loading spinner; suppresses clicks. |
| startIcon | `ReactNode` | — | Icon rendered before the label. |
| endIcon | `ReactNode` | — | Icon rendered after the label. |
| href | `string` | — | When set, renders the button as an anchor. |
| dropdownItems | `ButtonDropdownItem[]` | — | Inline split-button menu items. |

## Keyboard interactions

- **Enter** — Activates the button.
- **Space** — Activates the button.
- **Tab** — Moves focus to the next focusable element.
- **ArrowDown** — On a split button, opens the dropdown menu.

## Accessibility notes

- `aria-busy` applied while loading; click suppressed.
- `aria-disabled` mirrors `disabled`.
- Dropdown uses `aria-haspopup="menu"` + `aria-expanded`; items expose `role="menuitem"`.

<!-- exports: { "Button": "@sisyphos-ui/react" } -->