# Contrato · Botão

> Segue o modelo de `contratos/ruler.md`. Implementado em
> `app/src/componentes/Button/Button.tsx`, com CSS em `estilo/30-botao.css` —
> a **mesma** folha que o protótipo carrega, via `@import` em `11-v2-fila.css`.
> Uma cópia só: mudar aqui muda no protótipo.
>
> **Arquivo renomeado de `botao.md` em 21/set/2026**: o identificador de
> código virou `Button`/`variant`/`size` (era `Botao`/`peso`/`tamanho`),
> seguindo a convenção de nomenclatura do `bridge` (feedback do Abner, front-end
> sênior — ver `AGENTS.md`, seção Idioma). As classes CSS (`btn-marca`,
> `btn-contorno`...) não mudaram: são o contrato com `estilo/30-botao.css` e
> com o `landbank.html`, fora do escopo desta troca.

Dois eixos, **VARIANT × SIZE**, deliberadamente os mesmos do Badge: quem aprende
um componente aprende os dois.

## Anatomia

| Parte | Papel | Obrigatória |
|---|---|---|
| conteúdo | o rótulo da ação, em verbo | sim, exceto quando `iconOnly` |
| ícone | vem do conteúdo, não de prop. Tamanho é do componente (`--is`) | não |
| `aria-label` | nome acessível, via prop `label` | **sim, quando `iconOnly`** |

A borda de `1px solid transparent` na base é obrigatória e não é decoração: sem
ela, trocar a variant mudaria a altura em 2px e a linha da lista pularia.

## Props

| Prop | Tipo | Default | Observação |
|---|---|---|---|
| `variant` | `primary` \| `outline` \| `ghost` \| `danger` | **nenhum** | **Obrigatória.** Ver abaixo |
| `size` | `md` \| `sm` | `md` | `--is` cai junto: 15px → 14px |
| `iconOnly` | boolean | `false` | só ícone. 34×34 no `md`, 28×28 no `sm` |
| `full` | boolean | `false` | `flex:1` |
| `disabled` | boolean | `false` | `opacity:.45`, `pointer-events:none` |
| `label` | string | `undefined` | vira `aria-label`. Obrigatória com `iconOnly` |
| `type` | `button` \| `submit` \| `reset` | `button` | Ver abaixo |

### Por que `variant` não tem default

`.btn` sem classe de variant **não é um estado usado no produto**. Conferido no
protótipo: das 12 ocorrências de `.btn`, todas trazem `btn-1` (primary) ou `btn-2`
(outline). Um default inventaria um estado que ninguém desenhou, e o custo de
errar é um botão de ação primária aparecendo sem cor de marca. Botão sem variant não
compila.

Use os nomes **por função**. `btn-1` e `btn-2` continuam valendo no CSS como
apelido histórico, para o código antigo, e ficam fora da API nova.

### Por que `type` é `button` e não `submit`

O default do HTML é `submit`. Botão de ação dentro de um `<form>` disparando envio
sem ninguém pedir é bug clássico, e é silencioso: não gera erro, só um POST
inesperado. Há teste travando este default, justamente para impedir que alguém
"simplifique" a prop achando que o padrão do HTML basta.

## Estados

Os cinco, visíveis lado a lado em `Button.stories.tsx`, sem precisar interagir:

| Estado | O que muda | De onde vem |
|---|---|---|
| normal | por variant | `.btn-*` |
| hover | por variant, cada um tem o seu | `.btn-*:hover` |
| **pressionado** | `transform: scale(.975)` | regra global de `button:active` |
| foco | `outline: 3px solid var(--anel)`, offset 1px | **regra global** em `00-tokens.css` |
| desabilitado | `opacity:.45`, `pointer-events:none` | `.btn[disabled]` |

### Sobre "selecionado", que a Regra 4 pede e este componente não tem

Botão é ação momentânea, não guarda estado. O que existe é **pressionado**
(`:active`), e é isso que a vitrine mostra no lugar. Se o produto precisar de
botão de alternância, é **componente novo** com `aria-pressed`, não uma prop
booleana aqui — três booleanos são oito combinações.

### Sobre o foco, que a documentação antiga dizia não existir

`handoff/componentes.md` afirma que "não existe `.btn:focus-visible` no CSS de
hoje. Isso é um defeito, não uma decisão." **A afirmação está errada.** Existe uma
regra global `:focus-visible` em `00-tokens.css`, com token próprio (`--anel`) e o
racional escrito ali: halo translúcido de 3px em vez de retângulo duro de 2px.

Ela se aplica ao botão e a qualquer elemento aninhado. Medido no navegador em
25/ago/2026, depois da migração pra paleta neutra: `outline: 3px solid
color-mix(in srgb,var(--t2) 65%,transparent)`, 3,47:1 sobre `--papel`. Antes disso
o anel era roxo de marca e **nunca chegou a 3:1** (medido 1,9:1) — não era só a
cor errada, era o contraste errado. Ver CHANGELOG v0.7.0.

Uma estratégia de foco para o sistema inteiro é o desenho certo; uma por
componente é o errado, porque gera anel diferente por peça. **Não crie
`.btn:focus-visible`.** Há teste impedindo.

## Acessibilidade

- Elemento é `<button>`, nunca `<div>` clicável. Há teste.
- `iconOnly` **sem** `label` **quebra em desenvolvimento**, com exceção lançada. Não
  é aviso no console: aviso é ignorado, e o defeito só apareceria numa auditoria
  meses depois.
- `label` ausente resulta em `aria-label` **omitido**, nunca vazio. `aria-label=""`
  apaga o nome que o texto dos filhos (children) daria.
- Foco por teclado é obrigatório e vem da regra global.

## Fora do contrato

- **`BotaoCopiar`** (`.btn-copiar` em `21-check.css`) é componente próprio, não uma
  variant deste, porque tem estado de sucesso (`feito`) com volta temporizada.
- **`.btn-mini` e `.btn-txt`** (`02-shell.css`) existem no CSS, são usados uma vez
  cada no protótipo, e **não estão em nenhum contrato**. Não foram tocados.
- Ícone como prop. Ícone vem nos filhos (children); o componente só manda no tamanho.

## Pendências deste componente

- `ghost`, `danger`, `sm`, `iconOnly` e `full` existem no CSS e **não são usados em
  nenhum lugar do protótipo**. Então "visualmente igual ao protótipo" só pode ser
  verificado para `primary` e `outline` no `md`. O resto é oferta do sistema sem
  consumo comprovado, o que é aceitável num design system, mas precisa estar dito.
- Sem estado de carregamento. Um botão que dispara cálculo longo (o motor de
  decisão) não tem como dizer que está trabalhando. O `bridge` (Abner) já resolveu
  isso no `Button` dele com uma prop `loading` — candidato natural pra trazer pra
  cá numa próxima leva, fora do escopo deste rename.
