# Button

> Componentes / Formulário e ação · Estado: **Em andamento** — Implementado e utilizável, mas ainda sem a validação completa de acessibilidade.

O botão do sistema: executa uma ação no lugar — enviar, confirmar, abrir — ou, com `href`, vira um link com a mesma aparência. Tem oito variantes, três tamanhos e um estado de carregando que não tira o foco de quem apertou.

- Função: Formulário e ação
- Também chamado de: botão, CTA, chamada para ação, link de botão
- Mora em: `app/components/atoms/Button.tsx`
- Página: [https://phorte-design-system.vercel.app/styleguide/peca/Button](https://phorte-design-system.vercel.app/styleguide/peca/Button)
- Ficha no Storybook: [components-base-button](https://main--69b4847d498a9bd8659db9ba.chromatic.com/?path=/docs/components-base-button--docs)
- Prévia ao vivo, sozinha: [https://phorte-design-system.vercel.app/previa/Button/0](https://phorte-design-system.vercel.app/previa/Button/0)
- Todas as peças: [https://phorte-design-system.vercel.app/llms.txt](https://phorte-design-system.vercel.app/llms.txt)

## Como usar no código

```tsx
import { Button } from '@/app/components/atoms/Button';

<Button>…</Button>
```

## Uso

### Variantes na ficha

1. [Padrão](https://phorte-design-system.vercel.app/previa/Button/0)
2. [Todas As Variantes](https://phorte-design-system.vercel.app/previa/Button/1)
3. [secondary vs. neutral](https://phorte-design-system.vercel.app/previa/Button/2)
4. [Todos Os Tamanhos](https://phorte-design-system.vercel.app/previa/Button/3)
5. [Com ícones](https://phorte-design-system.vercel.app/previa/Button/4)
6. [Carregando](https://phorte-design-system.vercel.app/previa/Button/5)
7. [Desabilitado](https://phorte-design-system.vercel.app/previa/Button/6)
8. [Largura Total](https://phorte-design-system.vercel.app/previa/Button/7)
9. [forwardRef funciona](https://phorte-design-system.vercel.app/previa/Button/8)
10. [Como link](https://phorte-design-system.vercel.app/previa/Button/9)

Para a ação principal e as secundárias de uma tela ou formulário, e para navegar quando o destino precisa do peso visual de um botão ("Inscrever-se").

### Quando não usar

Para abrir uma lista de ações, use o Dropdown. Para escolher entre opções que ficam marcadas, o FilterGroup (filtro) ou o Radio (formulário). Para um link dentro de um parágrafo, texto com link — ou, no máximo, `variant="link"`.

### Faça

- Para a ação de apoio ou de escolha ao lado de uma primária ("Escolher esta turma", "Ver grade"), o `tonal`: verde claro, com o fio verde por dentro (E127). Não é o `secondary`, que é o verde cheio de confirmar.
- Escreva verbo e objeto, de uma a três palavras, sem ponto final: "Enviar inscrição".
- Use `href` para navegar e o `<button>` para agir; o componente decide a tag.
- Use `isLoading` durante o envio: o foco fica no botão, que anuncia `aria-busy` e ignora novos cliques.
- Passe `type="submit"` explicitamente no botão que envia o formulário — o padrão aqui é `type="button"`.
- Dê `aria-label` ao botão que só tem ícone.

### Evite

- Envolver o Button num `<a>`: são dois elementos interativos aninhados, e o leitor anuncia os dois.
- Desabilitar para dizer que falta algo: botão desabilitado sai do Tab e não explica o motivo. Se precisar, escreva ao lado o que falta.
- Mais de um `variant="primary"` na mesma área — a ação principal deixa de ser principal.
- Trocar `secondary` por cinza: `secondary` é verde desde setembro; o cinza é `neutral`.

### Nota de uso

`variant="secondary"` é **verde** desde a decisão de cor de setembro; o cinza de antes virou `variant="neutral"`. Alvo mínimo de 44×44 px em todos os tamanhos — a única exceção documentada é `variant="link"`, que é texto corrido dentro de um parágrafo. **`href` vira link** com a mesma aparência: use para navegar, e o `<button>` para agir — nunca `<a><Button /></a>`. `isLoading` **não desabilita**: o foco fica no botão, que anuncia `aria-busy` e ignora o clique. **Texto:** verbo e objeto, de uma a três palavras, sem ponto final — "Enviar inscrição", não "Clique aqui". **Evite desabilitar**: botão desabilitado não diz o que falta. Se precisar, escreva ao lado o motivo e como destravar.

### Anatomia

1. **Ícone à esquerda** — `leftIcon`, opcional e decorativo — o texto já descreve a ação.
2. **Rótulo** — O `children`: verbo e objeto. Com `isLoading`, fica escondido do leitor e o `loadingLabel` é lido no lugar.
3. **Ícone à direita** — `rightIcon`, opcional e decorativo.
4. **Indicador de carregando** — Um `Loader2` que gira só com movimento liberado (`motion-safe:animate-spin`).

### Estados

| Estado | O que muda |
| --- | --- |
| Padrão | Cor da variante; `primary` por padrão. |
| Hover | Tom mais escuro da variante (`hover:bg-primary-dark`, `hover:bg-secondary-dark`…); o `tonal` vira o verde cheio; `link` sublinha. |
| Foco | Anel `ring-ring` de 2px com afastamento de 2px, igual nas oito variantes; só no teclado (`focus-visible`). |
| Carregando | `isLoading`: ícone girando, `aria-busy="true"` e `aria-disabled="true"`, opacidade 50% e cursor de espera. Continua focável; o clique e o envio são bloqueados. |
| Desabilitado | `disabled`: opacidade 50%, sem ponteiro e fora da ordem de Tab. No modo link (`href`), `disabled` vira `aria-disabled` e o clique é bloqueado. |

### Comportamento

| Situação | O que acontece |
| --- | --- |
| Com `href` | Renderiza `next/link` com a aparência de botão. Com `target="_blank"`, o `rel="noopener noreferrer"` vem sozinho. |
| Sem `type` dentro de um formulário | Não envia: o padrão é `type="button"`. Um "Cancelar" não dispara o envio por engano. |
| Clique ou tecla com `isLoading` | Nada acontece — medido: Enter e Espaço não disparam o `onClick`. |
| Sem texto e sem `aria-label` | Em desenvolvimento, o console avisa: "Button: botão sem texto visível precisa de aria-label." |
| `variant="link"` | Perde a altura mínima de 44px e o preenchimento: é texto dentro de um parágrafo. É a única exceção de alvo documentada. |

### Relacionados

- [Dropdown](https://phorte-design-system.vercel.app/styleguide/md/Dropdown.md) — Um botão que abre uma lista de ações.
- [FilterGroup](https://phorte-design-system.vercel.app/styleguide/md/FilterGroup.md) — Pílulas que filtram na hora — uma ou várias.
- [FormField](https://phorte-design-system.vercel.app/styleguide/md/FormField.md) — O campo do formulário que o botão envia.

## Especificação

### Propriedades

| Prop | Tipo | Padrão | Obrigatória | Descrição |
| --- | --- | --- | --- | --- |
| `variant` | `'primary' \| 'secondary' \| 'neutral' \| 'outline' \| 'ghost' \| 'link' \| 'success' \| 'tonal' \| 'destructive'` | `'primary'` | não | Variante do `cva`. |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | não | Todos os tamanhos têm no mínimo 44px de altura — a trava de alvo de toque do relatório A11Y. O que muda entre eles é o padding horizontal e o tamanho do texto: um botão pequeno continua parecendo pequeno, sem que a área clicável fique abaixo do mínimo. |
| `fullWidth` | `boolean` | `false` | não | Variante do `cva`. |
| `href` | `string` | — | não | Vira um link (`next/link`) com a aparência de botão. Use para **navegar** — "Inscrever-se", "Ver detalhes" —, e o `<button>` para **agir** no lugar. Nunca `<a><Button /></a>`: são dois elementos interativos, um dentro do outro, e o leitor anuncia os dois. |
| `target` | `React.AnchorHTMLAttributes<HTMLAnchorElement>['target']` | — | não | Só com `href`. Com `_blank`, o `rel="noopener noreferrer"` vem sozinho. |
| `rel` | `string` | — | não |  |
| `isLoading` | `boolean` | `false` | não |  |
| `loadingLabel` | `string` | `'Carregando…'` | não | Anunciado por leitor de tela enquanto `isLoading`. Prefira algo específico: "Enviando inscrição…". |
| `leftIcon` | `React.ReactNode` | — | não | Ícone decorativo à esquerda. Passe `aria-hidden` nele — o rótulo do botão já descreve a ação. |
| `rightIcon` | `React.ReactNode` | — | não | Ícone decorativo à direita. Mesma regra do `leftIcon`. |

Aceita também: `React.ButtonHTMLAttributes<HTMLButtonElement>`.

### Medidas

| Parte | Valor | Classe |
| --- | --- | --- |
| Altura, `sm` e `md` | 44px no mínimo | `min-h-alvo-toque` |
| Altura, `lg` | 48px no mínimo | `min-h-12` |
| Anel de foco | 2px, afastado 2px | `focus-visible:ring-2 focus-visible:ring-offset-2` |

### Tokens de cor

| Token | Valor | Usado como |
| --- | --- | --- |
| `--destructive` | #BA0517 | `bg-destructive`, `bg-destructive/90` |
| `--primary` | #D42B2B | `bg-primary`, `text-primary` |
| `--secondary-dark` | #0D6B2E | `bg-secondary-dark`, `text-secondary-dark` |
| `--border` | #E5E5E5 | `bg-border` |
| `--border-interactive` | #8C8C8C | `border-border-interactive` |
| `--card` | #FFFFFF | `bg-card` |
| `--destructive-foreground` | #FFFFFF | `text-destructive-foreground` |
| `--foreground` | #1A1A1A | `text-ink` |
| `--muted` | #F4F6F9 | `bg-muted` |
| `--muted-foreground` | #595959 | `text-muted-foreground` |
| `--primary-dark` | #A81E1E | `bg-primary-dark` |
| `--primary-foreground` | #FFFFFF | `text-primary-foreground` |
| `--ring` | var(--footer-red)), decisão da revisão 2. Se outra peça precisar
   * dele como superfície, o nome vira de papel (ex.: --surface-brand-strong). */
  --footer-red: #7D1B1B | `ring-ring` |
| `--secondary` | #0B8434 | `bg-secondary` |
| `--secondary-foreground` | #FFFFFF | `text-secondary-foreground` |
| `--secondary-subtle` | #EAF4EE | `bg-secondary-subtle` |
| `--success` | #2E844A | `bg-success` |
| `--success-foreground` | #FFFFFF | `text-success-foreground` |
| `--success-strong` | #276F3E | `bg-success-strong` |

## Acessibilidade

### Teclado

| Tecla | Faz |
| --- | --- |
| Tab | Leva o foco ao botão. Desabilitado, ele é pulado; carregando, continua na ordem. |
| Enter / Espaço | Aciona o botão — medido: cada tecla dispara um clique. No modo link, só Enter segue o link, como em todo link. |

### Leitor de tela

- Com `isLoading`, o nome lido é o `loadingLabel` ("Carregando…" por padrão) — o rótulo visível fica `aria-hidden`. Prefira algo específico: "Enviando inscrição…".
- Carregando não é `disabled`: o botão continua na árvore e com o foco, e anuncia `aria-busy`.

### O que sustenta o estado

Usa os padrões: estatico. Falta ouvir com leitor de tela: estatico.

## Status e recursos

| Tipo | Recurso | Status |
| --- | --- | --- |
| Código | `app/components/atoms/Button.tsx` | Disponível |
| Ficha | https://main--69b4847d498a9bd8659db9ba.chromatic.com/?path=/docs/components-base-button--docs | Disponível |
| Teste de acessibilidade | axe em `npm run test` | No gate de todo commit |
| Leitor de tela | Passada da E33 | Falta ouvir |
| Design | Figma | Desativado até a Fase 2 |

### Histórico

- `0298af8` 2026-09-30 — E147: fechamento da Entrega 11 — REPORT.md, verificação cross-agent e o gate estático estrito
- Histórico completo no GitHub: https://github.com/srbunnie/Pos-Phorte-Design-System/commits/main/app/components/atoms/Button.tsx
