# Contrato · Toggle

> Segue o modelo de `contratos/ruler.md`. Implementado em
> `app/src/componentes/Toggle/Toggle.tsx`, com CSS em `estilo/11-v2-fila.css`
> (`.tgl`) — a **mesma** folha que o protótipo carrega.

> **Não é um dos 7 componentes da Fase 1.** `ds/gera.py` (`FASE_1`) e
> `app/src/paginas/Layout.tsx` (`COMPONENTES`) estão travados por teste em
> exatamente sete nomes — Toggle não é um deles, e não deveria entrar nessa
> lista: colocá-lo lá quebraria o teste que garante que ela é só a Fase 1, e
> inflaria a contagem — o mesmo defeito de número órfão que a Regra 1
> proíbe, só que ao contrário (contar demais em vez de de menos).
> **Tem página própria mesmo assim** — `/componentes/toggle`, listada em
> "Componentes adicionais" (barra lateral separada de "Componentes · N de
> 7"), não só na vitrine. A frase antiga aqui dizia o contrário; corrigida
> na auditoria de 21/set/2026.

A chave que liga e desliga uma camada — hoje usada nas camadas do mapa
("Estoque do entorno", "Eixo de transporte") e em outros pontos do produto
que mostram um estado binário controlado pelo usuário.

## Anatomia

| Parte | Papel | Obrigatória |
|---|---|---|
| trilha | a cápsula de fundo, `.tgl` | sim |
| bolinha | `.tgl::after`, desliza entre os dois lados | sim, é `::after`, não prop |

Não tem rótulo próprio: no protótipo, o texto que descreve o que a chave liga
é sempre um irmão (`.cam .nm`), nunca conteúdo do `.tgl`. O componente segue
essa composição — quem usa `Toggle` escreve o rótulo visível ao lado, por
fora.

## Props

| Prop | Tipo | Obrigatória | Observação |
|---|---|---|---|
| `checked` | `boolean` | sim | sem default: **controlado**, sem estado interno |
| `onCheckedChange` | `(proximo: boolean) => void` | sim | chamado com o oposto de `checked` no clique ou no Enter/Espaço |
| `label` | `string` | sim | nome acessível — ver Acessibilidade |
| `disabled` | `boolean` | não | ver Estados |

## Por que é controlado, sem modo não-controlado

Nenhum outro componente deste sistema tem duas formas de usar a mesma peça
(controlado vs. não-controlado com default interno). Adicionar a segunda
forma aqui seria a mesma duplicação que a Regra 2 proíbe no CSS, só que na
API: dois jeitos de fazer a mesma coisa que podem divergir em comportamento
sem nenhum aviso.

## Estados (Regra 4)

| Estado | Como | Observação |
|---|---|---|
| desligado | `checked={false}` | padrão visual, sem classe extra |
| ligado | `checked={true}` | aplica `.on` |
| hover | `:hover` | **sem estilo próprio no CSS original** — `.tgl` não tem regra `:hover`. Documentado, não inventado: ver "Achado construindo" abaixo |
| foco | `:focus-visible` | anel global do sistema, nenhum CSS de foco próprio aqui — mesma regra do Botão e do Badge |
| desabilitado | `disabled` | `.tgl[disabled]{opacity:.45;pointer-events:none}` — **CSS novo**, não existia. Ver abaixo |

## Achado construindo, não estava no CSS

Duas lacunas reais em `.tgl`, achadas comparando com `prototipo/index.html`:

1. **Sem estado de foco/teclado.** No protótipo, `.tgl` é `<div class="tgl on">`
   ou `<span class="tgl">` — sem `tabindex`, sem `role`, sem `aria-checked`.
   Hoje não há forma nenhuma de alcançar ou acionar a chave pelo teclado. O
   componente novo corrige isso na raiz: renderiza `<button role="switch">`
   de verdade, então foco e Enter/Espaço vêm de graça do HTML nativo, e o anel
   de foco global se aplica sozinho.
2. **Sem estado desabilitado.** Não existe `.tgl[disabled]` nem equivalente no
   CSS original — a Regra 4 exige os cinco estados sempre, então a regra foi
   adicionada em `estilo/11-v2-fila.css`, só opacidade (mesma convenção do
   `.btn[disabled]` do Botão), sem cor nova.
3. **Sem hover próprio.** Diferente das duas de cima, esta não foi corrigida:
   o CSS original realmente não muda a aparência no hover, e inventar um
   estilo de hover que o protótipo não tem seria o oposto do que este
   documento pede ("visualmente igual ao protótipo, não de memória"). Fica
   registrado como pendência, não como decisão silenciosa.

## Contraste, medido e corrigido durante a construção

A story "Em contexto" usa `.cam .nm small` (o rótulo secundário ao lado da
chave, ex. "18% absorvido em 6 meses"), que usava `color:var(--t4)`. O
`test:vitrine` (axe em Chromium real) reprovou: **4,19:1**, abaixo do 4,5:1
exigido pra texto de 11px — o mesmo defeito, na mesma medida, que
`contratos/botao-link.md` achou em `.pular` construindo este mesmo lote.
Corrigido para `--t2` (7,95:1) em `estilo/11-v2-fila.css`. Isto não é parte
do próprio `Toggle` (é o rótulo vizinho, escrito pelo consumidor), mas é CSS
compartilhado com o protótipo — a correção vale lá também.

## Acessibilidade

- `role="switch"` + `aria-checked` — o par que a ARIA define para este
  padrão exato (controle binário com estado persistente, diferente de
  `role="checkbox"` que é sobre seleção em formulário).
- `aria-label={label}`, obrigatório no tipo. Sem texto visível próprio, um
  toggle sem `label` anuncia "alternar" e mais nada — mesmo critério do
  `label` obrigatório no Botão só-ícone.
- Enter e Espaço ativam porque o elemento é um `<button>` nativo — nenhum
  `onKeyDown` escrito à mão.

## Fora do contrato

- **Não tem tamanho nem tom.** `.tgl` no CSS tem uma única aparência (30×17,
  cinza→`--tinta`). Se surgir necessidade de um toggle menor ou de outra cor,
  é decisão de design nova, não detalhe de implementação — mesma regra que
  `contratos/badge.md` registra pro ícone que o Badge não tem.

## Pendências deste componente

- **Hover não tem estilo próprio** — ver "Achado construindo" acima. Se um
  dia o produto quiser um hover real, o CSS precisa mudar primeiro (é o
  protótipo que manda, não o componente).
- ~~Sem página de documentação própria, sem entrada na barra lateral.~~
  **Resolvido** — ver nota no topo do contrato.
