@andespindola/crow-skills 0.41.0 → 0.42.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD-PARTY-NOTICES.md +28 -2
- package/catalog.json +4 -2
- package/crow_skills/__init__.py +1 -1
- package/crow_skills/broadcast.py +5 -2
- package/crow_skills/cli.py +37 -1
- package/package.json +1 -1
- package/release.json +135 -10
- package/skills/crow-design/NOTICE.md +26 -0
- package/skills/crow-design/SKILL.md +38 -9
- package/skills/crow-design/licenses/Apache-2.0.txt +191 -0
- package/skills/crow-design/licenses/Impeccable-NOTICE.md +11 -0
- package/skills/crow-design/licenses/design-system-MIT.txt +26 -0
- package/skills/crow-design/licenses/platform-design-MIT.txt +21 -0
- package/skills/crow-design/licenses/taste-skill-MIT.txt +21 -0
- package/skills/crow-design/references/aceite-e-pre-voo.md +267 -0
- package/skills/crow-design/references/adaptacao.md +152 -0
- package/skills/crow-design/references/anti-padroes.md +187 -0
- package/skills/crow-design/references/componentes-e-estados.md +312 -0
- package/skills/crow-design/references/contrato.md +2 -1
- package/skills/crow-design/references/cor.md +151 -0
- package/skills/crow-design/references/dados-e-densidade.md +194 -0
- package/skills/crow-design/references/direcao-estetica.md +209 -0
- package/skills/crow-design/references/documento-do-sistema.md +175 -0
- package/skills/crow-design/references/layout-e-espaco.md +134 -0
- package/skills/crow-design/references/leitura-do-brief.md +158 -0
- package/skills/crow-design/references/movimento.md +144 -0
- package/skills/crow-design/references/redesign-e-sistemas-existentes.md +91 -0
- package/skills/crow-design/references/registro-premium.md +154 -0
- package/skills/crow-design/references/sistema-de-tokens.md +264 -0
- package/skills/crow-design/references/superficie-e-profundidade.md +153 -0
- package/skills/crow-design/references/texto-e-conteudo.md +149 -0
- package/skills/crow-design/references/tipografia.md +139 -0
- package/skills/crow-design/scripts/validar_tokens.py +654 -0
- package/skills/crow-design/templates/tokens.exemplo.json +197 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
<!-- Derivado de Impeccable (Apache-2.0, Paul Bakaus), design-taste-frontend e high-end-visual-design (MIT, Leonxlnx), design-system (MIT, claudekit) e platform-design-skills (MIT, ehmo); reescrito e modificado pelo Crow em 2026-11-13. Ver ../NOTICE.md. -->
|
|
2
|
+
# Componentes e estados
|
|
3
|
+
|
|
4
|
+
Leia quando o contrato for especificar componentes, a matriz de estados, foco, desabilitado, alvos de toque, formulários, a escolha entre inline e modal, menus, botões, ícones, estados vazios, primeiro uso ou momentos de personalidade.
|
|
5
|
+
|
|
6
|
+
## Reuse antes de especificar
|
|
7
|
+
|
|
8
|
+
Prefira o componente existente que cumpre a intenção e diga que reusou. Componente novo entra quando nenhum existente serve. O vocabulário é consistente: o mesmo controle tem a mesma forma, o mesmo tamanho por densidade e o mesmo comportamento em toda a superfície; se o botão de salvar tem duas aparências, uma está errada.
|
|
9
|
+
|
|
10
|
+
**Extração para o sistema.** Um padrão só vira componente do sistema quando aparece três ou mais vezes com a mesma intenção (não só a mesma aparência). O contrato registra o plano: o componente, os tokens que consome, as variantes, os nomes, o que ele precisa de ARIA e teclado, e de onde ele substitui código avulso. Extrair um padrão usado uma vez, ou unificar dois parecidos com intenções diferentes, cria um componente que ninguém consegue usar sem exceção. Não invente componente para o documento do sistema "ter algo a mostrar": lista vazia é declarada vazia (ver [documento do sistema](documento-do-sistema.md)). Extrair e migrar o código é execução, de `crow-interface`.
|
|
11
|
+
|
|
12
|
+
## O vocabulário de estados
|
|
13
|
+
|
|
14
|
+
| Grupo | Estado | O que é |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| Interação | padrão | repouso, nada acontecendo |
|
|
17
|
+
| | hover | ponteiro fino sobre o controle; conforto, nunca única pista |
|
|
18
|
+
| | foco visível | alcançado por teclado (`:focus-visible`); aditivo, convive com qualquer outro |
|
|
19
|
+
| | ativo/pressionado | durante o clique ou toque; afunda (`translateY(1px)` ou `scale(.98)`), nunca sobe |
|
|
20
|
+
| | selecionado, marcado, indeterminado | escolha persistente (linha, aba, checkbox, opção); tem token próprio, não é hover mais forte |
|
|
21
|
+
| | expandido/recolhido | disclosure, menu, acordeão; espelha `aria-expanded` |
|
|
22
|
+
| | desabilitado | indisponível agora; com ou sem explicação (ver abaixo) |
|
|
23
|
+
| | somente leitura | visível e selecionável, não editável; não é desabilitado |
|
|
24
|
+
| | visitado | só em links de conteúdo, quando ajuda a navegar |
|
|
25
|
+
| Processo | carregando | operação em curso, determinada ou não |
|
|
26
|
+
| Resultado | erro/inválido | algo falhou ou o valor não passa na validação |
|
|
27
|
+
| | aviso | atenção antes de um problema |
|
|
28
|
+
| | sucesso | concluído; curto |
|
|
29
|
+
| | informação | contexto neutro |
|
|
30
|
+
| Conteúdo | vazio | não há dado (seis tipos, em [estado vazio](#estado-vazio)) |
|
|
31
|
+
| | parcial | parte chegou, parte falhou ou ainda vem |
|
|
32
|
+
| | texto longo e texto curto | nome de 60 caracteres e de 1; tradução 30 a 40% maior |
|
|
33
|
+
| | overflow | mais conteúdo que espaço: trunca com acesso ao inteiro, quebra ou rola |
|
|
34
|
+
| | um item e mil itens | a lista com 1 não vira cartão gigante; com 1.000 tem paginação ou virtualização decididas |
|
|
35
|
+
| | imagem ausente | arquivo quebrado, sem foto, carregando: fallback com a mesma proporção |
|
|
36
|
+
| Contexto | offline | sem rede: o que continua funcionando, o que fica em fila |
|
|
37
|
+
| | permissão negada | o recurso existe mas não para esta pessoa |
|
|
38
|
+
| | sessão expirada | quando há autenticação: preserva o trabalho e pede para entrar de novo |
|
|
39
|
+
|
|
40
|
+
**Prioridade visual quando estados coincidem:** desabilitado vence carregando, que vence ativo, que vence hover, que vence padrão. O foco visível não entra na disputa: ele é somado a qualquer estado, inclusive ao desabilitado com `aria-disabled` e ao carregando. Nenhum estado ou prioridade depende só de cor: forma, ícone, texto ou posição acompanham.
|
|
41
|
+
|
|
42
|
+
Cada estado vira token opaco (fundo, texto, borda, ícone) com par fg/bg declarado e verificado em cada modo (claro, escuro, alto contraste). Hover feito com o primário a 90% de opacidade muda o contraste conforme o fundo e não entra; selecionado como primário a 10% também não. Ver [cor](cor.md) e [sistema de tokens](sistema-de-tokens.md).
|
|
43
|
+
|
|
44
|
+
## A matriz por componente
|
|
45
|
+
|
|
46
|
+
O contrato traz uma matriz por componente: linhas são os estados pertinentes, colunas dizem a aparência (tokens), o texto exato, o comportamento (foco, teclado, anúncio) e o que verifica. Estado que não se aplica é marcado "não se aplica" com o motivo, para que a ausência seja decisão e não esquecimento. Estados obrigatórios por tipo:
|
|
47
|
+
|
|
48
|
+
| Componente | Estados que a matriz cobre |
|
|
49
|
+
|---|---|
|
|
50
|
+
| Botão | padrão, hover, foco, ativo, desabilitado, carregando; texto longo e curto |
|
|
51
|
+
| Link | padrão, hover, foco, ativo, visitado quando útil |
|
|
52
|
+
| Campo de texto | padrão, hover, foco, preenchido, desabilitado, somente leitura, inválido, válido quando informar ajuda, texto longo |
|
|
53
|
+
| Checkbox, radio, switch | desmarcado, marcado, indeterminado (checkbox), foco, desabilitado, inválido |
|
|
54
|
+
| Select, combobox | fechado, aberto, foco, opção ativa, selecionado, vazio de opções, carregando opções, desabilitado, inválido |
|
|
55
|
+
| Abas | ativa, inativa, hover, foco, desabilitada, overflow de abas |
|
|
56
|
+
| Menu, disclosure | fechado, aberto, item ativo, item desabilitado, foco |
|
|
57
|
+
| Lista, tabela | carregando, vazio (seis tipos), parcial, erro, sem permissão, 1 item, 1.000 itens, linha selecionada, linha em edição |
|
|
58
|
+
| Card | padrão, hover e foco quando clicável, carregando, imagem ausente, texto longo |
|
|
59
|
+
| Diálogo, sheet | abrindo, aberto, enviando, erro no envio, fechando |
|
|
60
|
+
| Toast, snackbar, alerta | informação, sucesso, aviso, erro; com e sem ação |
|
|
61
|
+
| Upload | vazio, arrastando sobre, enviando com progresso medido, sucesso, erro com tentar de novo, tipo ou tamanho recusado |
|
|
62
|
+
| Avatar, imagem | carregada, carregando, ausente (iniciais ou forma neutra), quebrada |
|
|
63
|
+
|
|
64
|
+
**Texto de cada estado.** A matriz escreve o texto, não "mensagem de erro aqui". Exemplos de forma (vocabulário completo em [texto e conteúdo](texto-e-conteudo.md)):
|
|
65
|
+
|
|
66
|
+
- Carregando: "Importando 3 arquivos…" (nomeia a operação; progresso só se medido: "Importando 2 de 3").
|
|
67
|
+
- Erro: "Não foi possível salvar o rascunho. Verifique a conexão e tente de novo." com a ação "Tentar de novo".
|
|
68
|
+
- Sucesso: "Fatura enviada."
|
|
69
|
+
- Vazio de primeiro uso: "Seus projetos aparecem aqui. Projetos reúnem tarefas e pessoas num só lugar." com "Criar projeto" e "Começar de um modelo".
|
|
70
|
+
- Permissão negada: "Você não tem acesso a este relatório. Peça acesso a quem administra a conta." com "Pedir acesso".
|
|
71
|
+
- Offline: "Sem conexão. Suas alterações ficam salvas aqui e são enviadas quando a conexão voltar."
|
|
72
|
+
|
|
73
|
+
## Estados de produto e falhas reais
|
|
74
|
+
|
|
75
|
+
A matriz cobre também o que acontece quando o mundo não colabora, não só o caminho feliz:
|
|
76
|
+
|
|
77
|
+
- **Erros de servidor mapeados para texto e ação:** requisição inválida (400) vira erro de validação no campo; não autenticado (401) leva a entrar de novo preservando o que estava sendo feito; sem permissão (403) explica e diz quem concede; não encontrado (404) diz o que não existe e oferece caminho; limite de requisições (429) diz quando tentar de novo; erro interno (500) não pede desculpas: nomeia o problema sem culpar ninguém, oferece tentar de novo e um caminho de suporte. Nenhum código aparece como mensagem principal.
|
|
78
|
+
- **Concorrência:** envio duplo bloqueado (o botão entra em carregando e não aceita segundo clique); atualização otimista tem desfazer automático com aviso quando o servidor recusa; edição concorrente avisa antes de sobrescrever.
|
|
79
|
+
- **Validação** no cliente para ajudar, sempre repetida no servidor; o erro do servidor aparece no mesmo lugar do erro do cliente.
|
|
80
|
+
- **Sessão expirada** não apaga o formulário.
|
|
81
|
+
- **Gestos interrompidos** (ver [adaptação](adaptacao.md)) devolvem o controle a um estado coerente.
|
|
82
|
+
|
|
83
|
+
Busca ao vivo e salvamento automático esperam a entrada assentar (algo como 300ms é ponto de partida) e eventos contínuos são amostrados (algo como 100ms); são heurísticas de custo, ajustadas à operação real, não regra.
|
|
84
|
+
|
|
85
|
+
## Foco
|
|
86
|
+
|
|
87
|
+
Foco visível em todo elemento interativo, com `:focus-visible`:
|
|
88
|
+
|
|
89
|
+
- Contorno de pelo menos **2px**, contraste de **3:1** contra a cor adjacente que o anel de fato toca: o fundo, quando há offset; o componente, quando o anel é interno (inset). **Offset de 2px** por padrão.
|
|
90
|
+
- Feito com `outline` real, ou com sombra acompanhada de `outline: 2px solid transparent`, para continuar aparecendo em `forced-colors` (onde sombras somem e o contorno transparente ganha a cor do sistema).
|
|
91
|
+
- Sólido: nada de anel a 20% de opacidade, nada de `outline: none` sem substituto.
|
|
92
|
+
- Cor do anel é token por modo; no escuro, é reverificada.
|
|
93
|
+
- O foco não fica escondido sob cabeçalho fixo, banner de cookies ou barra inferior (o elemento focado continua visível ao rolar por teclado).
|
|
94
|
+
- `:focus-within` pode destacar o grupo (campo com botão embutido, card com ações), sem substituir o anel do elemento.
|
|
95
|
+
- A ordem de foco segue a ordem visual. Modais movem o foco para dentro, prendem enquanto abertos e devolvem ao gatilho ao fechar.
|
|
96
|
+
|
|
97
|
+
Em redesign, os ganhos de foco que já existiam não regridem.
|
|
98
|
+
|
|
99
|
+
## Desabilitado
|
|
100
|
+
|
|
101
|
+
Desabilitar é a forma mais barata de esconder um motivo. Antes de desabilitar, considere deixar o controle ativo e explicar ao acionar ("Preencha o e-mail para continuar"), que ensina mais.
|
|
102
|
+
|
|
103
|
+
- **Quando a pessoa precisa saber por quê:** `aria-disabled="true"` num controle que continua focável, com a explicação acessível (texto próximo ligado por `aria-describedby`, ou tooltip que abre no foco e não só no hover). O clique não executa a ação. Não combine com `pointer-events: none`, que mata o tooltip e o foco.
|
|
104
|
+
- **Quando não há explicação a dar** (um passo que claramente ainda não chegou): `disabled` nativo, que sai da ordem de foco. Não use os dois atributos juntos.
|
|
105
|
+
- **Contraste:** texto de controle desabilitado está isento do 4.5:1 pela WCAG, mas o contrato mantém legível (como orientação, perto de 3:1) e usa tokens próprios de desabilitado, não opacidade de 50% sobre um fundo já apagado. Se o texto desabilitado carrega informação que a pessoa precisa ler, ele segue o 4.5:1.
|
|
106
|
+
- O cursor `not-allowed` é complemento, não sinal.
|
|
107
|
+
|
|
108
|
+
## Anúncios a tecnologias assistivas
|
|
109
|
+
|
|
110
|
+
Mudança de estado que a pessoa não vê precisa ser anunciada, e o mecanismo tem regras:
|
|
111
|
+
|
|
112
|
+
- **A região viva existe antes da mudança.** O contêiner com `role="status"` (educado, para progresso e sucesso) ou `role="alert"` (assertivo, para erro que exige atenção) está no DOM desde a carga, vazio; o texto é inserido quando o estado muda. Região criada junto com a mensagem não é anunciada, e `role="alert"` com texto já presente na carga anuncia na hora errada.
|
|
113
|
+
- **Carregando:** `aria-busy="true"` no contêiner que está sendo atualizado e uma frase curta na região viva ("Carregando faturas"); ao terminar, `aria-busy` sai e a região diz o resultado ("42 faturas carregadas").
|
|
114
|
+
- **Erro de campo:** `aria-invalid="true"` só depois da validação (nunca na carga de um campo vazio), `aria-describedby` apontando para a mensagem, e o resumo de erros do formulário anunciado na região viva no envio.
|
|
115
|
+
- Texto só para leitor de tela (`sr-only`) ligado por `aria-describedby` não é anunciado quando muda; ele descreve, não avisa.
|
|
116
|
+
|
|
117
|
+
## Campos e formulários
|
|
118
|
+
|
|
119
|
+
- **Rótulo persistente**, visível acima do campo (ou à esquerda em formulários densos de desktop), ligado ao campo. Placeholder é exemplo do formato ("nome@exemplo.test"), nunca rótulo, e quando existe tem 4.5:1 como qualquer texto.
|
|
120
|
+
- **Borda do campo com 3:1** contra o fundo em cada modo (claro, escuro, alto contraste); é o limite do componente. Hairline decorativa a 1.2:1 só em superfície não interativa.
|
|
121
|
+
- **Requisitos antes do envio:** formato, tamanho, obrigatoriedade e elegibilidade ditos junto do campo, não descobertos no erro. Obrigatório e opcional com tratamento consistente na superfície inteira (marque o que for minoria).
|
|
122
|
+
- **Texto de ajuda** presente no markup, ligado por `aria-describedby`, responde a pergunta implícita em vez de repetir o rótulo.
|
|
123
|
+
- **Erro inline:** abaixo do campo, com ícone e texto, ligado ao campo, nomeando o que corrigir sem culpar ("Use pelo menos 8 caracteres", não "Senha inválida"). Some quando o valor fica válido. Validar ao sair do campo ou no envio, não a cada tecla; o formulário nunca é apagado. Em formulário longo, o envio com erro leva o foco ao primeiro campo inválido e mostra um resumo com links para cada erro.
|
|
124
|
+
- **Contraste do conjunto:** valor digitado, placeholder, anel de foco, ajuda e erro passam AA contra o fundo da seção, não contra um branco imaginário.
|
|
125
|
+
- **Entrada certa:** `type` e `inputmode` corretos (o teclado numérico aparece para número), `autocomplete` com o token certo, campo de 16px no celular.
|
|
126
|
+
- **Controles de escolha:** checkbox com estados marcado e indeterminado; grupo de radio dentro de `fieldset` com `legend`; switch só para efeito imediato (ligar e desligar agora), com `role="switch"`, nunca para escolha que depende de um "Salvar"; select nativo quando a lista é curta e simples, combobox com busca quando é longa. A área de toque inclui o rótulo.
|
|
127
|
+
- **Área de texto** cresce com o conteúdo até um limite e mostra contador quando há limite de caracteres.
|
|
128
|
+
|
|
129
|
+
## Alvos
|
|
130
|
+
|
|
131
|
+
- **Piso WCAG 2.2 AA (2.5.8): 24×24 CSS px**, ou espaçamento que garanta um círculo de 24px sem sobrepor o vizinho.
|
|
132
|
+
- **Recomendado em toque: 44×44** (CSS px na web, pt no iOS); **Android 48×48dp com 8dp** entre alvos.
|
|
133
|
+
- Em `pointer: coarse`, o token de densidade aplica 44 aos controles.
|
|
134
|
+
- A área de toque pode ser maior que a marca visível (padding ou pseudo-elemento); ícone de 16px num botão de 44 é normal.
|
|
135
|
+
- Controles de 32px servem em interface de ponteiro fino quando espaçados. Badge e rótulo não são alvos; se forem clicáveis, viram botão com alvo.
|
|
136
|
+
|
|
137
|
+
## Carregamento: forma do conteúdo, não roda girando
|
|
138
|
+
|
|
139
|
+
- **Skeleton** com a forma e a altura do conteúdo final (linhas de tabela na densidade escolhida, bloco de imagem na proporção certa, linhas de texto com larguras variadas), para que nada salte quando o conteúdo chega.
|
|
140
|
+
- **Spinner** só inline, dentro do controle que disparou uma ação curta (o botão "Salvando…"), nunca no meio de uma área de conteúdo, de card ou de página.
|
|
141
|
+
- Nada de opacidade reduzida no texto durante o carregamento: ele cai abaixo de AA. O botão em carregamento mantém a largura (o rótulo vira "Salvando…" ou o indicador entra ao lado) para não deslocar o layout.
|
|
142
|
+
- Espera curta não precisa de indicador que pisca: mostrar o esqueleto só depois de uma pequena fração de segundo evita o lampejo (heurística; cede quando a espera é sempre longa).
|
|
143
|
+
- Recarga de dado já exibido mantém o anterior visível e marcado como atualizando.
|
|
144
|
+
- Progresso determinado quando a operação sabe quanto falta; indeterminado com a operação nomeada quando não sabe. Nunca progresso inventado nem trabalho falso para encenar.
|
|
145
|
+
- Movimento do esqueleto (brilho que passa) para com movimento reduzido.
|
|
146
|
+
|
|
147
|
+
## Onde mora a interação: árvore de decisão
|
|
148
|
+
|
|
149
|
+
Escolha nesta ordem e pare no primeiro que serve:
|
|
150
|
+
|
|
151
|
+
1. **Inline:** a ação acontece onde está o conteúdo (editar no lugar, expandir a linha, confirmar com desfazer).
|
|
152
|
+
2. **Disclosure:** detalhe que aparece sob demanda no mesmo fluxo (acordeão, "mostrar mais", `<details>`).
|
|
153
|
+
3. **Popover ou sheet:** escolha contextual curta que não bloqueia o resto (menu, seletor de data, painel lateral, bottom sheet no celular).
|
|
154
|
+
4. **Modal:** só para interrupção real (decisão que precisa ser tomada antes de qualquer outra coisa, ação destrutiva sem desfazer possível) ou foco protegido (fluxo que não pode se misturar ao resto, como pagamento).
|
|
155
|
+
|
|
156
|
+
Modal como primeira ideia é preguiça de arquitetura; esconder complexidade atrás de modais transforma uma tela em várias. Desfazer é preferível a confirmar. Fluxo longo dentro de modal vira página ou passo inline. Sobreposições escapam do contêiner que as disparou (`<dialog>`, Popover API, `position: fixed` ou portal; ver [espaço e layout](layout-e-espaco.md)) e usam a escala de camadas tokenizada, canônica em [superfície e profundidade](superficie-e-profundidade.md).
|
|
157
|
+
|
|
158
|
+
**Especificação do diálogo:**
|
|
159
|
+
- `<dialog>` nativo aberto como modal, ou o padrão de diálogo acessível equivalente, com `aria-modal="true"` e `aria-labelledby` apontando para o título.
|
|
160
|
+
- Foco inicial no primeiro elemento útil (o campo, ou a ação menos destrutiva numa confirmação), preso dentro enquanto aberto, devolvido ao gatilho ao fechar.
|
|
161
|
+
- Esc fecha, salvo quando fechar perde trabalho (aí pergunta). Clique no fundo fecha só em diálogos sem dados digitados.
|
|
162
|
+
- Rolagem da página travada por trás; conteúdo do diálogo rola por dentro com cabeçalho e rodapé fixos.
|
|
163
|
+
- Fundo escurecido é token semântico por modo, não `rgb(0 0 0 / .5)` literal igual nos dois temas.
|
|
164
|
+
- Botões nomeados pelo resultado: "Excluir projeto" e "Manter projeto", nunca "Confirmar" e "Cancelar" soltos. A ação destrutiva usa a variante destrutiva e não é o foco inicial.
|
|
165
|
+
- Larguras de referência: 384, 512, 640 e 768px, e largura total menos 32px no celular (ou sheet). Sem altura fixa.
|
|
166
|
+
|
|
167
|
+
## Menus, disclosure e navegação recolhida
|
|
168
|
+
|
|
169
|
+
Esta é a especificação canônica de acessibilidade de menu, disclosure e menu em overlay; as outras referências linkam para cá em vez de repeti-la.
|
|
170
|
+
|
|
171
|
+
- O gatilho é um `<button>` com nome acessível, `aria-expanded` refletindo o estado e `aria-controls` apontando para o painel.
|
|
172
|
+
- **Disclosure** (mostrar e esconder conteúdo): Enter e Espaço alternam; o foco fica no gatilho; Tab segue a ordem natural para dentro do conteúdo aberto.
|
|
173
|
+
- **Menu de ações** (lista de comandos): setas navegam entre itens, Home e End vão às pontas, digitar uma letra pula ao item, Enter executa, Esc fecha e devolve o foco ao gatilho, Tab fecha e segue. O papel `menu` é para menus de comandos de aplicação; uma lista de links de navegação é navegação (`<nav>` com lista), não `menu`.
|
|
174
|
+
- **Menu em tela cheia** (navegação recolhida no celular) é um diálogo: foco preso, Esc fecha, foco volta ao botão; o botão que vira X mantém o nome acessível atualizado ("Abrir menu" / "Fechar menu").
|
|
175
|
+
- Submenus abrem por clique ou teclado, não só por hover, e não fecham quando o ponteiro atravessa a diagonal até eles.
|
|
176
|
+
|
|
177
|
+
## Toast, snackbar e alerta
|
|
178
|
+
|
|
179
|
+
- Toast ou snackbar só para o transitório: confirmação de algo que já aconteceu, com desfazer quando útil. Erro que exige ação fica inline, perto da causa; nada de diálogo nativo do navegador.
|
|
180
|
+
- Mensagem transitória com ação não some antes de a pessoa conseguir agir: pausa no hover e no foco, tempo de exibição suficiente para ler e alcançar a ação, ou não some sozinha.
|
|
181
|
+
- Anúncio pela região viva certa: `role="status"` para sucesso e informação, `role="alert"` para erro e aviso urgentes.
|
|
182
|
+
- **Alerta** (aviso fixo na página): variantes informação, sucesso, aviso e destrutivo, com fundo tingido, ícone que carrega o significado junto com o texto, título e descrição; o botão de fechar tem nome acessível ("Fechar aviso"), não só "×"; o par de texto sobre o fundo tingido é verificado (amarelo claro com texto escuro é onde mais se erra). Padding de 16px como referência. A borda lateral colorida grossa não é o indicador (ver [anti-padrões](anti-padroes.md)).
|
|
183
|
+
|
|
184
|
+
## Botões
|
|
185
|
+
|
|
186
|
+
**Rótulo.** Verbo mais objeto que descreve o resultado: "Salvar alterações", "Enviar convite", "Exportar relatório". Nada de "OK", "Sim", "Enviar" soltos. Ação destrutiva nomeia o objeto e a consequência ("Excluir 3 arquivos"). Uma ação mantém o mesmo nome do começo ao fim.
|
|
187
|
+
|
|
188
|
+
**Uma primária por vista.** Em cada região de decisão há no máximo uma ação primária; as demais descem na hierarquia. Duas primárias lado a lado são uma pergunta que o design não respondeu. Um rótulo por intenção: "Fale conosco", "Entre em contato" e "Vamos conversar" na mesma página são a mesma ação, escolha um; o mesmo vale para cadastro ("Começar", "Teste grátis") e para portfólio ("Ver trabalhos").
|
|
189
|
+
|
|
190
|
+
**Hierarquia de variantes:** primária (cheia, acento), secundária (neutra cheia ou tonal), contornada (terciária, borda de 3:1 quando é o único limite, pelo papel `semantic.border.control`), fantasma (sem fundo, para ações discretas em barras de ferramentas), link (navegação, não ação), destrutiva (para ação que apaga ou encerra; usada só onde a destruição é a ação principal da vista).
|
|
191
|
+
|
|
192
|
+
**Tamanhos** (altura, padding horizontal, fonte, ícone), como ponto de partida a ajustar à marca:
|
|
193
|
+
|
|
194
|
+
| Tamanho | Altura | Padding horizontal | Fonte | Ícone | Observação |
|
|
195
|
+
|---|---|---|---|---|---|
|
|
196
|
+
| Pequeno | 32px | 12px | 14px | 16px | ponteiro fino e espaçado; em `pointer: coarse` sobe para 44 |
|
|
197
|
+
| Padrão | 40px | 16px | 14–16px | 18px | em `pointer: coarse` sobe para 44 |
|
|
198
|
+
| Grande | 48px | 24px | 16px | 20px | atende toque |
|
|
199
|
+
| Só ícone | 40px quadrado | 0 | — | 18px | nome acessível obrigatório; 44 em toque |
|
|
200
|
+
|
|
201
|
+
**Estados opacos.** Hover e pressionado usam tokens opacos (um passo mais escuro ou mais claro, conforme o modo), não alfa. Pressionado afunda. Carregando mantém a largura e não reduz a opacidade do rótulo. Desabilitado segue a seção acima.
|
|
202
|
+
|
|
203
|
+
**Contraste.** O rótulo é texto normal: 4.5:1, salvo se for de fato texto grande (≥24px, ou ≥18,66px em negrito). Botão fantasma sobre foto tem scrim ou contorno; branco sobre branco não existe.
|
|
204
|
+
|
|
205
|
+
**Texto e largura.** CTA principal cabe em uma linha no desktop; rótulo curto (de preferência uma a três palavras, medido pela largura e pela tradução, não por contagem em inglês) e sem `max-width` forçado que quebra a linha.
|
|
206
|
+
|
|
207
|
+
**Botão-ilha** (pílula com ícone final dentro de um círculo encostado no padding) é técnica do registro premium, com guarda-corpos em [registro premium](registro-premium.md).
|
|
208
|
+
|
|
209
|
+
## Ícones em botão e na interface
|
|
210
|
+
|
|
211
|
+
- Uma biblioteca e uma espessura, acompanhando o peso do texto (traço de 1.5 ou 2 nas bibliotecas de contorno); traço ultrafino em tamanho pequeno perde legibilidade. A família que o projeto já usa, quando existe.
|
|
212
|
+
- Ícone que carrega significado sozinho tem contraste de 3:1 e nome acessível; ícone ao lado de texto é decorativo (`aria-hidden="true"`) e não repete o rótulo para o leitor de tela.
|
|
213
|
+
- Botão só com ícone tem nome acessível e tooltip com o mesmo nome, aberto no foco e no hover. Navegação só com ícones, sem rótulo, não entra.
|
|
214
|
+
- Tamanho do ícone acompanha o tamanho do botão (16, 18, 20px) e alinha à linha de base do texto.
|
|
215
|
+
- Seta anexada a todo botão e link é cromo de template: a seta entra quando indica direção ou saída (link externo, próximo passo).
|
|
216
|
+
- Ícones direcionais espelham em RTL (ver [adaptação](adaptacao.md)).
|
|
217
|
+
- Glifo Unicode e emoji não são sistema de ícones. Ícone de biblioteca real ou SVG autoral, inline entre 16 e 24px; SVG grande desenhado à mão fazendo papel de ilustração não é ícone.
|
|
218
|
+
|
|
219
|
+
## Superfícies do navegador
|
|
220
|
+
|
|
221
|
+
Seleção de texto, cursor de texto, barra de rolagem, anel de foco e sublinhado de links fazem parte do contrato; a especificação canônica está em [superfície e profundidade](superficie-e-profundidade.md), seção Superfícies do navegador.
|
|
222
|
+
|
|
223
|
+
## Especificações de referência
|
|
224
|
+
|
|
225
|
+
Para quando o projeto não tem sistema e o contrato precisa de um ponto de partida. Valores em px, nomes de token semânticos; tudo ajustado à marca e com os defeitos das referências comuns já corrigidos.
|
|
226
|
+
|
|
227
|
+
- **Campo de texto:** alturas 32 / 40 / 48px (padding 8×12, 8×12, 12×16; fonte 14, 14 e 16px, e 16px em qualquer tamanho no celular). Borda com 3:1 em repouso pelo papel `semantic.border.control`, que é o limite do controle; `border.default` é decorativo (divisor, contorno de card) e não serve de borda de campo (o cinza muito claro de muitos kits fica em 1.2 a 1.5:1 e reprova), borda mais forte no hover, anel de foco sólido de 2px com offset (não o anel a 20%), erro com borda e ícone de erro mais texto. Desabilitado com tokens próprios, somente leitura sem caixa de edição. Anatomia: rótulo, ícone inicial opcional, valor, ação final opcional (limpar, mostrar senha, com nome acessível), ajuda ou erro.
|
|
228
|
+
- **Card:** padrão (borda fina **ou** sombra pequena, conforme a linguagem de elevação declarada; não as duas, que formam o card fantasma), elevado (sombra maior, sem borda), contornado. Card clicável inteiro tem um único alvo (o título como link que cobre o card), foco visível no card todo e não usa elevação no hover como único sinal. Padding de 24px (16 no compacto), gap de 16px; cabeçalho, conteúdo, rodapé. Raio pelo sistema da marca, com raio interno igual ao externo menos o padding.
|
|
229
|
+
- **Badge:** variantes padrão, secundária, contornada, destrutiva, sucesso, aviso e informação; alturas 20, 24 e 28px com padding 4×8, 4×10 e 6×12px (sm, md, lg); texto de 12px ou mais (rótulo de badge é um dos papéis em que 12px é permitido; 11px reprova legibilidade em muitos públicos) com 4.5:1 e sem ser a única portadora da informação. Branco sobre verde médio e branco sobre azul médio costumam ficar entre 3.3 e 3.7:1: escureça o fundo ou use texto escuro. Badge não é alvo; badge de status leva texto, não só a cor.
|
|
230
|
+
- **Alerta:** ver acima.
|
|
231
|
+
- **Diálogo:** ver acima.
|
|
232
|
+
- **Tabela:** em [dados e densidade](dados-e-densidade.md) (densidades 40/48/56px, padding de célula 12×16, números à direita com `tabular-nums`, status à esquerda junto do rótulo).
|
|
233
|
+
- **Tooltip:** texto curto e não essencial, abre no hover e no foco, fecha com Esc, não contém ação nem link, não cobre o gatilho, fica visível enquanto o ponteiro estiver sobre ele.
|
|
234
|
+
- **Popover:** como o tooltip, mas pode conter ação; abre por clique, prende o foco só se for modal.
|
|
235
|
+
- **Abas:** lista de abas com seta para navegar entre elas e Tab para o painel; aba ativa marcada por mais que cor (sublinhado, peso, fundo); quando não cabem, rolagem com indicação ou menu de excedentes.
|
|
236
|
+
- **Paginação:** página atual marcada e anunciada (`aria-current="page"`), anterior e próxima com rótulo, alvo de 24px ou mais.
|
|
237
|
+
- **Barra de progresso:** só com progresso medido; `role="progressbar"` com valor; rótulo da operação.
|
|
238
|
+
- **Avatar:** imagem com fallback de iniciais ou forma neutra na mesma proporção; nome acessível da pessoa, não "avatar".
|
|
239
|
+
- **Navegação:** item atual marcado (`aria-current`), rótulos visíveis, ordem estável entre telas.
|
|
240
|
+
|
|
241
|
+
## Componentes nativos
|
|
242
|
+
|
|
243
|
+
Em app nativo, o default são os controles da plataforma, e reinventá-los por estilo é o vício mais comum:
|
|
244
|
+
|
|
245
|
+
- **iOS:** switch, segmented control, stepper, pickers do sistema, action sheet, alerta, menu de contexto, ações de deslizar em listas, listas agrupadas e inset para ajustes, sheet para subtarefa dispensável com Cancelar e Concluído, SF Symbols.
|
|
246
|
+
- **Android:** botões cheio, tonal, contornado e de texto; um FAB para a única ação primária da tela; switch, chips, snackbar (nunca toast para feedback acionável), bottom sheet, diálogo do Material só para decisão que precisa interromper, barra de navegação, rail ou drawer conforme a largura; Material Symbols.
|
|
247
|
+
|
|
248
|
+
A tradução entre as duas plataformas e as convenções de navegação estão em [adaptação](adaptacao.md).
|
|
249
|
+
|
|
250
|
+
## Estado vazio
|
|
251
|
+
|
|
252
|
+
Vazio é convite a agir, não "sem dados". Um vazio bem feito tem cinco partes:
|
|
253
|
+
|
|
254
|
+
1. **O que vai aparecer aqui:** "Seus relatórios aparecem aqui."
|
|
255
|
+
2. **Por que importa:** "Relatórios mostram a evolução das vendas por região."
|
|
256
|
+
3. **Como começar:** a ação principal, nomeada ("Criar relatório"), e uma alternativa quando houver ("Começar de um modelo", "Importar").
|
|
257
|
+
4. **Interesse visual, só se autoral:** ilustração do mundo do produto ou nenhuma. Ícone genérico grande, ilustração de banco ou cena em SVG estilo rascunho pioram; a ação clara vem antes de qualquer imagem.
|
|
258
|
+
5. **Ajuda contextual:** um caminho curto para quem trava ("Ver como funciona").
|
|
259
|
+
|
|
260
|
+
E seis tipos, lista canônica que as outras referências usam: **primeiro uso; limpo pelo usuário; sem resultado de busca; filtro sem resultado; sem permissão; falha ao carregar.** O filtro sem resultado é separado da busca sem resultado porque a saída é outra (remover o filtro, não reescrever a busca), e a falha ao carregar está na lista porque se parece com vazio sem ser: tratá-la como "nada aqui" mente sobre os dados. Cada um tem seu tom e sua saída:
|
|
261
|
+
|
|
262
|
+
| Tipo | Situação | O que diz e oferece |
|
|
263
|
+
|---|---|---|
|
|
264
|
+
| Primeiro uso | nunca usou o recurso | valor e primeiro passo, modelo ou exemplo |
|
|
265
|
+
| Limpo pelo usuário | apagou tudo de propósito | toque leve, recriar fácil, sem comemorar nem lamentar |
|
|
266
|
+
| Sem resultado de busca | busca não achou | qual consulta, sugestão de ajuste, limpar busca |
|
|
267
|
+
| Filtro sem resultado | filtros excluíram tudo | qual filtro, remover aquele filtro, limpar todos |
|
|
268
|
+
| Sem permissão | não pode ver | por quê, quem concede, pedir acesso |
|
|
269
|
+
| Falha ao carregar (é erro, não vazio) | não carregou | o que aconteceu, tentar de novo, e não fingir que não há dados |
|
|
270
|
+
|
|
271
|
+
## Primeiro uso e onboarding
|
|
272
|
+
|
|
273
|
+
O trabalho do onboarding não é ensinar o produto; é levar a pessoa o mais rápido possível ao momento que prova que o produto vale o tempo dela. O contrato declara esse momento e o nível de experiência do público.
|
|
274
|
+
|
|
275
|
+
- **Mostrar em vez de contar:** exemplos funcionando, funcionalidade real dentro do onboarding (não um modo tutorial separado), uma coisa por vez.
|
|
276
|
+
- **Opcional:** quem já sabe pula; nada bloqueia o produto; "Pular" visível e fácil.
|
|
277
|
+
- **Tempo até o valor:** os conceitos mais importantes primeiro; o resto descoberto no contexto, quando a pessoa encontra o recurso.
|
|
278
|
+
- **Contexto acima de cerimônia:** o vazio é a melhor oportunidade de onboarding; dicas aparecem no ponto de uso.
|
|
279
|
+
- **Respeito:** sem explicar o óbvio, sem tom condescendente.
|
|
280
|
+
|
|
281
|
+
Estrutura do primeiro uso: boas-vindas (o que é, o que vai conseguir, estimativa honesta de tempo, opção de pular), configuração mínima (só o indispensável, com o motivo de cada pedido e bons defaults), **um a três conceitos** centrais (interativos quando possível, com "passo 1 de 3"), primeiro sucesso real (com modelo ou dado de exemplo rotulado, comemoração proporcional e próximo passo claro).
|
|
282
|
+
|
|
283
|
+
- **Dicas contextuais:** aparecem na primeira vez que a pessoa vê o recurso, apontam para o elemento, trazem explicação e benefício curtos, são dispensáveis com "Não mostrar de novo", acessíveis por teclado e anunciadas.
|
|
284
|
+
- **Tours guiados** só para interface complexa, mudança grande ou ferramenta de domínio: de **3 a 7 passos**, destacando o elemento sem bloquear o resto da interface (a pessoa pode clicar no que quiser), com "Pular tour", reabríveis pelo menu de ajuda, focados em fluxo ("Crie um projeto") e não em inventário ("Este é o botão de projetos"), com dado de exemplo para que as ações funcionem. O destaque escurece ao redor sem capturar o clique fora dele.
|
|
285
|
+
- **Persistência do "visto":** a conclusão e cada dispensa ficam registradas (no perfil quando há conta, localmente quando não há), e o mesmo onboarding não aparece duas vezes; quem volta não revê a introdução.
|
|
286
|
+
- **Anúncio de novidade:** mostra o que mudou e por que importa, deixa testar na hora, dispensável.
|
|
287
|
+
|
|
288
|
+
## Momentos de personalidade
|
|
289
|
+
|
|
290
|
+
Personalidade não é camada de graça genérica; é o caráter do produto aparecendo numa interação útil, numa resposta humana ou num detalhe inesperadamente cuidadoso. Em Persuadir e Experienciar, ela pode correr pela voz, composição, movimento e descoberta, desde que a obra continue no centro. Em Operar e Ler, ela se concentra em momentos (primeiro uso, conclusão, recuperação, domínio) e a confiabilidade carrega o resto.
|
|
291
|
+
|
|
292
|
+
**Tese em uma frase:** o que a pessoa deve sentir e por que esse sentimento pertence a este produto. Depois, o menor sistema que entrega: uma resposta característica a uma ação que importa, linguagem do produto que esclarece carregando a voz, uma transição com comportamento material reconhecível, um detalhe sonoro, tátil ou visual do mundo do produto, ou uma descoberta que revela utilidade real.
|
|
293
|
+
|
|
294
|
+
**Momentos:**
|
|
295
|
+
- Sucesso proporcional ao esforço e à consequência: marcos podem crescer; salvar rotineiro só precisa parecer certo.
|
|
296
|
+
- Espera verdadeira: progresso real ou contexto útil; nunca fingir trabalho ou atrasar a conclusão para encenar.
|
|
297
|
+
- Vazio e primeiro uso: a próxima ação clara antes da personalidade.
|
|
298
|
+
- Erro e recuperação: o problema e a saída primeiro; calor humano reduz estresse.
|
|
299
|
+
- Repetição: continua agradável no centésimo uso; variação só se continuar coerente e previsível.
|
|
300
|
+
- Descoberta: recompensa a curiosidade sem esconder função necessária.
|
|
301
|
+
|
|
302
|
+
**As seis proteções, que não se negociam:**
|
|
303
|
+
1. Não atrasa, não bloqueia e não esconde a tarefa principal.
|
|
304
|
+
2. Não passa por cima de convenção de plataforma nem de acessibilidade (leitor de tela, teclado, toque, movimento reduzido, localização e contexto cultural).
|
|
305
|
+
3. Não acrescenta alegação factual que ninguém pediu.
|
|
306
|
+
4. Som só com consentimento e respeitando o modo silencioso.
|
|
307
|
+
5. Nada obrigatório, impossível de pular ou cansativo na repetição.
|
|
308
|
+
6. Custo (dependência, asset, peso) proporcional ao momento.
|
|
309
|
+
|
|
310
|
+
E uma regra de tom: nada de piada em perda de dados, dinheiro, privacidade, acesso perdido ou trabalho bloqueado. Loops não essenciais param quando saem da tela.
|
|
311
|
+
|
|
312
|
+
**Checagem:** o momento é específico a ponto de um produto vizinho não poder usá-lo sem mudança; melhora compreensão, confiança, motivação ou recuperação; a interface continua rápida e óbvia sem o floreio; funciona sem som, por teclado, por toque e traduzida.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
<!-- Derivado de Impeccable (Apache-2.0, Paul Bakaus), design-taste-frontend e high-end-visual-design (MIT, Leonxlnx), design-system (MIT, claudekit) e platform-design-skills (MIT, ehmo); reescrito e modificado pelo Crow em 2026-11-13. Ver ../NOTICE.md. -->
|
|
1
2
|
# O que o contrato precisa decidir, e onde a decisão costuma faltar
|
|
2
3
|
|
|
3
4
|
Leia ao montar o design-contract. Cada bloco é uma decisão que, sem estar escrita aqui, vai ser inventada de novo na implementação — em cada tela, de um jeito diferente.
|
|
@@ -10,7 +11,7 @@ A geração de interface converge para um punhado de aparências que aparecem in
|
|
|
10
11
|
- Fundo quase preto com um único acento ácido.
|
|
11
12
|
- Gradiente violeta-azul como identidade.
|
|
12
13
|
- Conteúdo picado em cards idênticos, mesmo raio em tudo, mesma sombra cinza embaixo de cada um, gradiente como decoração.
|
|
13
|
-
- Cromo de template que aparece em qualquer assunto: eyebrow em caixa alta espaçada acima de todo título, metadado colado por ponto médio, rótulo em "PALAVRA — fragmento", preto
|
|
14
|
+
- Cromo de template que aparece em qualquer assunto: eyebrow em caixa alta espaçada acima de todo título, metadado colado por ponto médio, rótulo em "PALAVRA — fragmento", "preto" de moda puxado para outra família de cor (marrom, azul) no lugar de um quase-preto neutro ou levemente tingido pelo matiz da marca, monoespaçada como fantasia de técnico, seta anexada ao texto de botão e link.
|
|
14
15
|
- Três cards iguais como fileira de features, dashboard com sidebar à esquerda por reflexo, hero com número grande e estatísticas de apoio.
|
|
15
16
|
|
|
16
17
|
O teste é simples: a mesma direção sairia de um brief sobre outro assunto? Se sim, decida de novo a partir do que o assunto tem de específico — matéria, vocabulário, público, tarefa. Onde o brief fixa a direção, o brief ganha, inclusive quando ele pede um desses looks.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
<!-- Derivado de Impeccable (Apache-2.0, Paul Bakaus), design-taste-frontend e high-end-visual-design (MIT, Leonxlnx), design-system (MIT, claudekit) e platform-design-skills (MIT, ehmo); reescrito e modificado pelo Crow em 2026-11-13. Ver ../NOTICE.md. -->
|
|
2
|
+
# Cor
|
|
3
|
+
|
|
4
|
+
Leia quando o contrato for fixar estratégia de cor, papéis, rampas, modos (claro, escuro, alto contraste), cores de dados ou pares de contraste, ou quando uma paleta existente precisar ser estendida sem perder a marca.
|
|
5
|
+
|
|
6
|
+
## Estratégia antes das cores
|
|
7
|
+
|
|
8
|
+
Antes de escolher um único matiz, decida quanto da superfície a cor ocupa. A estratégia governa mais a leitura da página do que o matiz escolhido.
|
|
9
|
+
|
|
10
|
+
| Estratégia | O que é | Onde costuma valer |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| **Restrained** | Neutros mais um acento. O acento marca ação primária, seleção e estado. | Piso de Operar; padrão de Ler. Produto denso, documentação, setor público. |
|
|
13
|
+
| **Committed** | Uma cor saturada cobre de 30% a 60% da superfície, em regiões inteiras. | Persuadir com marca de cor forte; páginas de campanha. |
|
|
14
|
+
| **Full palette** | Três ou quatro papéis cromáticos nomeados, cada um com função. | Experienciar, marcas ilustrativas, produtos infantis, editorial com seções codificadas. |
|
|
15
|
+
| **Drenched** | A superfície é a cor; o neutro vira exceção. | Experienciar, peças de lançamento, obra que lidera. |
|
|
16
|
+
|
|
17
|
+
Os percentuais de Committed são uma heurística de leitura (abaixo de 30% a cor vira acento; acima de 60% vira Drenched), não uma medida a auditar com régua. Em Operar, Restrained é o piso: a cor fica reservada para o que exige atenção, porque cor espalhada em ferramenta de trabalho compete com o estado. Escape: a marca registrada ou o brief fixam outra estratégia, e a exceção entra no documento do sistema.
|
|
18
|
+
|
|
19
|
+
Em Restrained, uma referência útil é o acento ocupar no máximo cerca de 10% de qualquer tela; se ele aparece em tudo, deixou de apontar. Cede quando a tela é pequena e quase toda ação (um checkout de uma etapa) ou quando a estratégia é outra.
|
|
20
|
+
|
|
21
|
+
**Cor em escala de região.** Quando a cor se compromete, ela ocupa áreas (uma faixa, um painel, o fundo de uma seção), não pontinhos espalhados. Vinte acentos pequenos na mesma cor leem como ruído; uma região colorida lê como decisão.
|
|
22
|
+
|
|
23
|
+
**Claro ou escuro por cena física.** Nunca escolha o modo pela categoria ("ferramenta de dev é escura", "saúde é clara"). Escreva uma frase de cena: quem usa, onde, sob que luz, por quanto tempo. Um painel de plantão noturno pede escuro; um app de receita usado na bancada, claro. Quais modos existem (só claro, só escuro, os dois, alto contraste) é decisão do contrato com o dono, não obrigação automática: quando há dois, os dois são desenhados e testados com paridade de hierarquia e de marca.
|
|
24
|
+
|
|
25
|
+
## Papéis, não amostras
|
|
26
|
+
|
|
27
|
+
O contrato define papéis; amostras soltas viram incoerência na terceira tela. Papéis mínimos:
|
|
28
|
+
|
|
29
|
+
- **Superfície:** canvas (fundo da página), elevado (painel, card, popover), rebaixado (poço, campo), sobreposição (scrim de modal).
|
|
30
|
+
- **Texto:** primário, secundário, desabilitado informativo, texto sobre ação, texto sobre cada cor de status.
|
|
31
|
+
- **Interação:** ação (primária, secundária), foco, seleção, link e link visitado quando relevante.
|
|
32
|
+
- **Borda:** `border.default`, divisória decorativa, e `border.control`, limite de campo e de controle, com 3:1 ou mais em todos os modos.
|
|
33
|
+
- **Status:** sucesso, aviso, erro, info, cada um com fundo, texto e ícone.
|
|
34
|
+
- **Dados:** categórica, sequencial, divergente (ver abaixo).
|
|
35
|
+
|
|
36
|
+
Em produto, padronize o vocabulário de dez estados semânticos: hover, focus, active, disabled, selected, loading, error, warning, success, info. O acento aparece só em ação primária, seleção e estado; barras laterais e toolbars ganham uma segunda camada neutra, não o acento. Acento saturado em item inativo é um dos vícios mais comuns de produto: faz o inativo gritar mais que o ativo.
|
|
37
|
+
|
|
38
|
+
Os papéis viram tokens semânticos que apontam para primitivos; a troca de tema remapeia só os semânticos. A arquitetura de camadas, os modos e a validação estão em [sistema de tokens](sistema-de-tokens.md).
|
|
39
|
+
|
|
40
|
+
## Espaço de cor e rampas
|
|
41
|
+
|
|
42
|
+
- **Projeto existente:** mantenha o espaço de cor que ele usa (hex, HSL, `rgb()`, P3). Converter tudo para OKLCH no meio de um redesign é mudança de contrato, não refinamento.
|
|
43
|
+
- **Paleta nova na web:** OKLCH. A luminosidade (L) é perceptualmente uniforme, o que torna previsível o contraste entre degraus e permite derivar estados (hover, ativo) movendo L sem mudar o matiz.
|
|
44
|
+
- **Matiz pelo significado, não pela categoria.** "Fintech é azul" e "saúde é verde" produzem a página que todo concorrente já tem.
|
|
45
|
+
|
|
46
|
+
**Rampas com croma caindo nas pontas.** Uma rampa varia L de cerca de 15% (escuro) a cerca de 95% (claro), com oito a dez passos. O croma (C) **não** fica constante: ele atinge o pico nos degraus médios e cai perto do branco e do preto. Croma alto em L extremo sai do gamut sRGB, é recortado de forma diferente por cada navegador e produz claros berrantes e escuros lamacentos. O formato do template de exemplo mostra a curva: no teal do exemplo, o degrau 100 tem C 0.025, o 500 tem C 0.105 e o 900 volta a 0.04.
|
|
47
|
+
|
|
48
|
+
Depois de montar a rampa, faça **clamp de gamut**: cada valor precisa existir em sRGB (e, se o projeto usa P3, ter uma versão P3 com fallback sRGB declarado). O validador avisa quando uma cor `oklch()` está fora do gamut.
|
|
49
|
+
|
|
50
|
+
**Cor explícita em vez de pilhas de transparência.** Texto, borda e superfície são cores opacas resolvidas. Alpha em texto ou borda muda o contraste conforme o fundo em que cai, e o fundo muda com o modo, com a imagem atrás e com o estado. Alpha só entra quando o contraste foi verificado sobre o fundo real, em cada modo; para estados (hover, selecionado), prefira tokens opacos (`action-hover`) a `primary/90` ou `primary/10%`.
|
|
51
|
+
|
|
52
|
+
## Neutros
|
|
53
|
+
|
|
54
|
+
Regra única: o cinza é **neutro** ou **tingido pelo matiz da marca**, e a escolha fica registrada no documento do sistema. Neutro puro é válido e costuma ser o certo em produto denso e em marcas de várias cores; tingido dá coesão quando há um matiz dominante (croma muito baixo, da ordem de 0.004 a 0.012 em OKLCH). O que não se faz é misturar cinzas quentes e frios na mesma página ou alternar entre duas famílias de cinza conforme a tela.
|
|
55
|
+
|
|
56
|
+
**Sem `#000` e `#fff` puros como default.** Preto e branco absolutos achatam a profundidade, aumentam o halo (texto claro em fundo preto "vibra") e deixam sem degrau para a elevação. Use off-black e off-white tokenizados. Cede quando a marca é preto e branco puros, no modo de alto contraste e em `forced-colors`, onde o sistema decide.
|
|
57
|
+
|
|
58
|
+
**Texto sobre cor deriva do matiz.** Texto secundário sobre uma superfície colorida sai do matiz dessa superfície (mais claro ou mais escuro, com o mesmo H), nunca de um cinza genérico, que sobre cor parece sujo e quase sempre perde contraste.
|
|
59
|
+
|
|
60
|
+
## Acento
|
|
61
|
+
|
|
62
|
+
- **Um acento por padrão.** Um segundo só entra com função distinta (por exemplo, o primeiro para ação e o segundo para seleção ou para uma categoria de dado), e a função fica escrita.
|
|
63
|
+
- **Saturação contida (regra única, em OKLCH; as outras referências linkam esta).** O croma do acento fica **abaixo de cerca de 80% do croma máximo que o gamut alvo (sRGB, ou P3 com fallback sRGB) permite para aquele L e H**. Exemplo de cálculo: em L 0.6 e H 250, o maior C ainda dentro do sRGB é por volta de 0.169, então o acento fica em C 0.135 ou menos. É heurística, não medida de conformidade. Motivo: no limite do gamut o acento cansa, domina a página e é recortado de forma diferente por cada navegador e tela. Escape: a cor registrada da marca vence (e a exceção entra no documento do sistema), e um momento único em Committed ou Drenched pode encostar no limite quando o contraste foi verificado. A regra de 80% das origens era em HSL; em OKLCH ela vira esta fração do croma disponível.
|
|
64
|
+
- **Trava de consistência de cor:** o acento escolhido vale para a página inteira e para o produto inteiro. CTA azul numa página de cinzas quentes, badge teal num site rosa e um segundo acento "só nesta seção" quebram a leitura do sistema. A trava é derivada da marca; ela não impede a marca de ter vários papéis cromáticos documentados (Full palette).
|
|
65
|
+
- **Trava de tema da página:** uma página tem um tema; seções invertidas (uma faixa escura no meio de uma página clara) não entram por padrão. Exceção nomeada: um único bloco de cor como momento narrativo. Variações tonais da mesma família (dois degraus próximos de neutro escuro lado a lado) não contam como inversão.
|
|
66
|
+
|
|
67
|
+
## Defaults recusáveis da categoria
|
|
68
|
+
|
|
69
|
+
Estas escolhas aparecem em quase toda interface gerada e por isso não dizem nada sobre o assunto. São defaults recusáveis: o brief ou a marca liberam, e a exceção fica registrada com motivo.
|
|
70
|
+
|
|
71
|
+
- **Roxo e azul "de IA":** gradientes violeta-azul, glows automáticos, neon. A saída padrão é neutro (família de cinza frio, neutro ou pedra) com um acento único escolhido pelo significado; uma marca que é roxa continua roxa, executada com intenção.
|
|
72
|
+
- **Paleta "premium consumer":** fundos bege e creme (`#f5f1ea`, `#f7f5f1`, `#fbf8f1`, `#efeae0`, `#ece6db`, `#faf7f1`, `#e8dfcb`), acentos latão, ferrugem e vinho (`#b08947`, `#b6553a`, `#9a2436`, `#9c6e2a`, `#bc7c3a`, `#7d5621`) e texto espresso (`#1a1714`, `#1a1814`, `#1b1814`). É o default de luxo de quem não decidiu. Entra quando a marca nomeia essas cores ou o assunto é genuinamente vintage, artesanal ou de herança.
|
|
73
|
+
- **Clusters nomeados:** creme quente com serifa display de alto contraste e acento terracota; quase-preto com um acento neon e bordas brilhando; hairlines de jornal com serifa itálica e rótulos mono espaçados. Também o fundo que puxa para o "prior de renderização": creme quente no claro e ardósia azul-preta no escuro.
|
|
74
|
+
- **Teste:** se dá para adivinhar a paleta pela categoria do produto (ou pela categoria mais o que ela costuma evitar), refaça.
|
|
75
|
+
|
|
76
|
+
Para a saída neutra com acento único, exemplos de acento das origens, **como inspiração e não obrigação**: Emerald (verde-esmeralda), Electric Blue (azul elétrico), Deep Rose (rosa profundo), Burnt Orange (laranja queimado), sobre neutros frios ou de pedra. Cada um ainda passa pela regra de saturação contida acima, pelos pares de contraste e pelo teste de adivinhar a paleta pela categoria; o matiz continua vindo do significado, não da lista.
|
|
77
|
+
|
|
78
|
+
Famílias alternativas servem como **inspiração de pesquisa**, não como rodízio obrigatório: luxo frio (prata, cromo, fumaça); floresta (verde profundo, osso, acento âmbar); preto e castanho; cobalto com papel claro; terracota com ardósia; oliva, tijolo e papel; monocromo com um único ponto saturado. Nenhuma é melhor que a paleta derivada do brief, e não existe memória confiável de "o projeto anterior usou X": variação existe contra defaults genéricos, nunca contra a marca. Duas delas encostam em clusters acima (papel creme, terracota) e pedem o mesmo cuidado.
|
|
79
|
+
|
|
80
|
+
## Contraste: WCAG 2.2 AA é o piso
|
|
81
|
+
|
|
82
|
+
| Elemento | Mínimo AA | Observação |
|
|
83
|
+
|---|---|---|
|
|
84
|
+
| Texto normal | 4.5:1 | Inclui placeholder, helper, rótulo, texto de botão. |
|
|
85
|
+
| Texto grande | 3:1 | Grande = **≥24px regular ou ≥18.66px bold** (18pt / 14pt bold). "18px" regular não é texto grande. |
|
|
86
|
+
| Não-texto | 3:1 | Limite de controle (borda de input, checkbox), ícone que informa, indicador de foco, segmentos de gráfico que carregam significado. |
|
|
87
|
+
| AAA (opcional) | 7:1 texto, 4.5:1 grande | Declarado no contrato quando adotado; não é meta implícita. |
|
|
88
|
+
|
|
89
|
+
Como verificar:
|
|
90
|
+
|
|
91
|
+
- **Por par fg/bg declarado**, não por cor solta. Cada par que o contrato usa (texto sobre canvas, texto sobre elevado, texto sobre ação, ícone sobre status) entra na tabela de contraste dos tokens.
|
|
92
|
+
- **Por estado:** hover, ativo, selecionado, foco e desabilitado que ainda informa algo. Desabilitado puramente inerte está isento pela WCAG, mas se o usuário precisa entender o que está desabilitado, ele precisa ser legível.
|
|
93
|
+
- **Por modo:** claro, escuro e alto contraste, cada um com sua tabela. Um par que passa no claro não prova nada no escuro.
|
|
94
|
+
- **Texto sobre imagem e sobre campo saturado** (Committed, Drenched): verifique no ponto mais desfavorável da imagem, ou use scrim ou superfície opaca.
|
|
95
|
+
- **Toda redução de intensidade reverifica.** Dessaturar, clarear o texto secundário ou "suavizar" uma página são as operações que mais quebram AA em silêncio.
|
|
96
|
+
|
|
97
|
+
### Pares que falham com frequência
|
|
98
|
+
|
|
99
|
+
Valores calculados pela fórmula WCAG 2.x com cores de paletas utilitárias comuns:
|
|
100
|
+
|
|
101
|
+
| Par | Razão | Por que falha | Correção |
|
|
102
|
+
|---|---|---|---|
|
|
103
|
+
| Branco sobre azul-500 (`#3B82F6`) | 3.68 | Texto de botão de 14 a 16px precisa de 4.5 | Azul-600 (`#2563EB`): 5.17 |
|
|
104
|
+
| Branco sobre verde-600 (`#16A34A`) | 3.30 | Badge de sucesso com texto pequeno | Verde-700 (`#15803D`): 5.02 |
|
|
105
|
+
| Branco sobre vermelho-500 (`#EF4444`) | 3.76 | Botão destrutivo | Vermelho-600 (`#DC2626`): 4.83 |
|
|
106
|
+
| Branco sobre amarelo-500 (`#EAB308`) | 1.92 | Aviso com texto branco | Texto escuro (`#111827`): 9.25 |
|
|
107
|
+
| Cinza-500 sobre cinza-100 (`#6B7280` / `#F3F4F6`) | 4.39 | Cabeçalho de tabela, texto "muted" | Cinza-600 (`#4B5563`): 6.87 |
|
|
108
|
+
| Cinza-400 como placeholder (`#9CA3AF` sobre branco) | 2.54 | Placeholder também é texto | Cinza-500 (`#6B7280`): 4.83 |
|
|
109
|
+
| Borda cinza-200 sobre branco (`#E5E7EB`) | 1.24 | Limite de input invisível (1.4.11) | `border.control` com cinza-500: 4.83 |
|
|
110
|
+
| Borda cinza-300 sobre branco (`#D1D5DB`) | 1.47 | Idem | Idem |
|
|
111
|
+
| Borda cinza-800 sobre cinza-950 no escuro | 1.37 | Limite de input no escuro | `border.control` com cinza-500 sobre cinza-950: 4.16 |
|
|
112
|
+
| Anel de foco azul-500 a 20% sobre branco | 1.26 | Foco precisa de 3:1 | Anel sólido de 2px, offset 2px, ≥3:1 contra a cor que ele toca |
|
|
113
|
+
| Hairline preta a 5% sobre branco | 1.12 | Só serve como decoração | Se o elemento é interativo, limite real ≥3:1 |
|
|
114
|
+
| Hairline branca a 10% sobre `#050505` | 1.22 | Idem no escuro | Idem |
|
|
115
|
+
| Card branco sobre canvas `#F9FAFB` | 1.05 | A separação desaparece | Tint mais forte, sombra calculada ou borda; 3:1 se a borda é o único limite de algo interativo |
|
|
116
|
+
|
|
117
|
+
Hairlines e separações de card decorativas não precisam de 3:1; o limite de um controle (`border.control`) e o único indicador de estado precisam.
|
|
118
|
+
|
|
119
|
+
**Anel de foco.** `focus.ring` precisa de 3:1 ou mais contra a cor adjacente que ele **de fato toca**: com offset de 2px para fora, é o fundo em volta do componente (por isso o template declara os pares `focus.ring`/`surface.canvas` e `focus.ring`/`surface.raised`, um para cada superfície onde há controle); quando o anel é interno (inset), é a cor do próprio componente. Cada modo tem seus pares. Largura, offset e estados do foco em [componentes e estados](componentes-e-estados.md).
|
|
120
|
+
|
|
121
|
+
## Pistas além da cor
|
|
122
|
+
|
|
123
|
+
Estado, categoria e prioridade nunca dependem só de cor. Erro tem ícone e texto; link no meio de texto tem sublinhado (ou outra pista além do matiz); série de gráfico tem rótulo direto, forma ou padrão. Simule protanopia, deuteranopia, tritanopia e acromatopsia nas paletas de status e de dados antes de fechar o contrato.
|
|
124
|
+
|
|
125
|
+
## Modo escuro, alto contraste e `forced-colors`
|
|
126
|
+
|
|
127
|
+
- **Escuro desenhado, não invertido.** A elevação no escuro sobe pela luminosidade: superfícies mais altas ficam um pouco mais claras, porque sombra quase não se vê em fundo escuro. Saturação dos acentos costuma baixar um pouco para não vibrar. Texto claro em fundo escuro ganha entrelinha e tracking levemente maiores (ver [tipografia](tipografia.md)).
|
|
128
|
+
- **Matriz completa por modo.** Primário, anel de foco, status, destrutivo, scrim e superfícies elevadas são sobrescritos no modo escuro com contraste verificado; modo que só troca fundo e texto deixa botões e badges sem checagem.
|
|
129
|
+
- **Alto contraste** é um modo de tokens quando o contrato o adota: bordas reais em todo controle, texto com 7:1, nada de informação em sombra ou transparência.
|
|
130
|
+
- **`forced-colors: active`:** o sistema substitui as cores. Tudo que dependia de `box-shadow`, gradiente ou fundo para existir (anel de foco, limite de card, estado selecionado) precisa de `outline` ou `border` real. Use as cores de sistema (`Canvas`, `CanvasText`, `LinkText`, `Highlight`, `ButtonText`) quando precisar ajustar; não tente reimpor a paleta da marca.
|
|
131
|
+
- **Toggle de tema** respeita `prefers-color-scheme` por padrão, persiste a escolha explícita do usuário e declara `color-scheme` para que controles nativos e barras de rolagem acompanhem.
|
|
132
|
+
|
|
133
|
+
## Cores de dados
|
|
134
|
+
|
|
135
|
+
- **Categórica:** matizes distintos com luminosidade próxima (para nenhuma série parecer mais importante) e validados contra daltonismo; em geral até seis a oito categorias, depois disso agrupe em "outros" ou use rótulos.
|
|
136
|
+
- **Sequencial:** um matiz (ou dois próximos) variando L de forma monotônica; o mais escuro ou o mais saturado significa "mais".
|
|
137
|
+
- **Divergente:** dois matizes saindo de um ponto médio neutro com significado (zero, meta, média).
|
|
138
|
+
- Diferencie também por L, por C, por forma, por padrão ou por rótulo direto; cinza neutro para "sem dado" ou contexto.
|
|
139
|
+
- O acento da marca não precisa ser a primeira série; reservá-lo para destaque (a série que a pessoa veio ver) costuma comunicar mais.
|
|
140
|
+
|
|
141
|
+
Regras de tabela, gráfico e densidade em [dados e densidade](dados-e-densidade.md).
|
|
142
|
+
|
|
143
|
+
## Nativo
|
|
144
|
+
|
|
145
|
+
- **iOS:** cores semânticas do sistema (`label`, `secondaryLabel`, `systemBackground` e afins) para que claro, escuro e aumento de contraste funcionem de graça; um tint de marca; materiais do sistema em vez de vidro feito à mão.
|
|
146
|
+
- **Android:** papéis de cor do Material (primary, on-primary, surface, surface-container e variantes); Dynamic Color no Android 12 ou superior **com fallback estático** da marca para versões anteriores e para quando o usuário desliga; elevação tonal (a superfície mais alta fica mais tingida) em vez de sombra.
|
|
147
|
+
- Os dois temas são compostos e testados em aparelho. Detalhes de plataforma em [adaptação](adaptacao.md).
|
|
148
|
+
|
|
149
|
+
## Como medir
|
|
150
|
+
|
|
151
|
+
Os pares de contraste vivem no arquivo de tokens, em `$contrast`, por modo. Rode `python3 scripts/validar_tokens.py ARQUIVO.json` (ou `--json` para saída estruturada): ele resolve referências, aceita cores hex, `rgb()` e `oklch()`, rejeita alpha, avisa gamut e reprova par abaixo do mínimo declarado em qualquer modo. Formato e camadas em [sistema de tokens](sistema-de-tokens.md); o gate entra em [aceite e pré-voo](aceite-e-pre-voo.md).
|