# Button

> La acción. Siete variantes, seis tamaños, estado de carga y forma de píldora para marketing.

```tsx
import { Button } from "sebs7n-ui/button"
```

## Ejemplos

### Variantes

```tsx
import { Button } from "sebs7n-ui/button"

function Variantes() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button>Guardar</Button>
      <Button variant="accent">Nueva factura</Button>
      <Button variant="outline">Exportar</Button>
      <Button variant="secondary">Duplicar</Button>
      <Button variant="ghost">Cancelar</Button>
      <Button variant="destructive">Eliminar</Button>
      <Button variant="link">Ver detalle</Button>
    </div>
  )
}
```

### Tamaños e íconos

```tsx
import { ArrowRightIcon, PlusIcon, TrashIcon } from "lucide-react"
import { Button } from "sebs7n-ui/button"

function Tamanos() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button size="sm">Pequeño</Button>
      <Button size="md">Mediano</Button>
      <Button size="lg">Grande</Button>
      <Button size="icon-sm" aria-label="Agregar">
        <PlusIcon />
      </Button>
      <Button size="icon-md" variant="outline" aria-label="Eliminar">
        <TrashIcon />
      </Button>
      <Button>
        Continuar <ArrowRightIcon />
      </Button>
    </div>
  )
}
```

### Carga

El ancho no cambia y el click queda cancelado mientras dura.

```tsx
import { Button } from "sebs7n-ui/button"
import { useState } from "react"

function Carga() {
  const [guardando, setGuardando] = useState(false)
  return (
    <Button
      loading={guardando}
      onClick={() => {
        setGuardando(true)
        setTimeout(() => setGuardando(false), 1600)
      }}
    >
      Guardar cambios
    </Button>
  )
}
```

### Píldora, solo para marketing

`shape="pill"` en los CTA de un hero. Nunca en el chrome de una app.

```tsx
import { Button } from "sebs7n-ui/button"

function Pildora() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      <Button shape="pill" size="lg">
        Empezar gratis
      </Button>
      <Button shape="pill" size="lg" variant="outline">
        Hablar con ventas
      </Button>
    </div>
  )
}
```

### Un link con forma de botón

`buttonVariants()` sobre un `<a>`: sigue siendo un link para el lector de pantalla.

```tsx
import { buttonVariants } from "sebs7n-ui/variants/button"

function ComoLink() {
  return (
    <a className={buttonVariants({ variant: "accent" })} href="/docs/instalacion">
      Ir a la instalación
    </a>
  )
}
```

## Props

### Button

Hereda las props de `Button`.

| Prop | Tipo | Por defecto | Descripción |
|---|---|---|---|
| `loading` | `boolean` | `false` | Muestra el spinner encima del contenido y cancela el `onClick`. El ancho no cambia. |
| `onClick` | `MouseEventHandler<T>` | — | Se ignora mientras `loading` está activo. |
| `shape` | `"default" \| "pill"` | — | La forma del botón. `pill` es `rounded-full` con un escalón más de padding horizontal: en una curva completa, el texto que empieza donde empezaba en un rectángulo queda pegado al borde. **Solo para los CTA de un hero o de una sección de marketing.** Es la regla de vercel.com, donde los dos CTA del hero son píldoras y el resto del sitio no: mezclar las dos formas en la misma pantalla se ve descuidado, así que en el chrome de una app —nav, tablas, formularios, diálogos— no va nunca. |
| `size` | `"sm" \| "md" \| "lg" \| "icon-sm" \| "icon-md" \| "icon-lg"` | — | `sm` 32px · `md` 40px · `lg` 48px, más los tres `icon-*` cuadrados. |
| `variant` | `"link" \| "default" \| "outline" \| "secondary" \| "ghost" \| "accent" \| "destructive"` | — | `default` (negro) · `accent` (marca) · `outline` · `secondary` · `ghost` · `destructive` · `link`. |
| `className` | `string` | — | — |

## Teclado

| Tecla | Qué hace |
|---|---|
| Enter | Activa el botón. |
| Espacio | Activa el botón. |
| Tab | Entra y sale. Un botón `disabled` sigue en el orden de tabulación porque Base UI usa `data-disabled`, no el atributo nativo. |

## Accesibilidad

- `loading` pone `aria-busy` y `aria-disabled`, y cancela el `onClick`: el botón se lee como ocupado en vez de desaparecer del foco.
- El anillo de foco (`focus-visible:focus-ring`) usa `brand-700` y no se saca nunca.
- En `size="icon-*"` hace falta `aria-label`: no hay texto que leer.
- El texto sobre `variant="accent"` llega a 4,5:1 en claro y en oscuro; hay un test que lo recalcula desde OKLCH.

## Reglas de uso

- **Un solo acento por pantalla.** `variant="accent"` para la acción principal; el CTA por defecto es el negro (`variant="default"`).
- **`shape="pill"` solo en los CTA de un hero o de una sección de marketing.** Nunca en el chrome de una app —nav, tablas, formularios, diálogos—: dos formas de botón en la misma pantalla se leen como un descuido.
- **Un link con forma de botón es un `<a>`**: `className={buttonVariants({ variant })}` sobre `<Link>`. No uses `render` para links, que Base UI les pone `role="button"`.
- `variant="destructive"` solo cuando la acción borra algo, y siempre detrás de un `AlertDialog`.
- `loading` no reemplaza al `disabled` del formulario: deshabilitá también el submit si no querés dobles envíos.

## Relacionados

[badge](/docs/components/badge.md) · [dropdown-menu](/docs/components/dropdown-menu.md) · [alert-dialog](/docs/components/alert-dialog.md)
