Acessibilidade
WCAG 2.2 AA é o piso — e o canon diz onde
Os 21 critérios de sucesso da WCAG que o canon cita, cada um com as seções onde aparece (medido nas 276 fatias); depois, os contratos que todo componente, bancada e página cumprem — os 7 testes, a acessibilidade dos fundamentos, o contrato de bancada (BT) e a camada de máquina (MK). Trechos byte a byte, com procedência.
Critérios WCAG citados no canon
- SC 1.3.1citado em 2 seçõesComponentes §67, Componentes §68
- SC 1.3.5citado em 2 seçõesComponentes §61, Componentes §66
- SC 1.4.1citado em 2 seçõesComponentes §65, Componentes §90
- SC 1.4.3citado em 11 seçõesMapa de cobertura §17, Mapa de cobertura §18, Mapa de cobertura §22, Componentes §cabeçalho e mais 7
- SC 1.4.10citado em 7 seçõesComponentes §cabeçalho, Componentes §43-b, Componentes §57, Componentes §58 e mais 3
- SC 1.4.11citado em 24 seçõesMapa de cobertura §11, Mapa de cobertura §15, Mapa de cobertura §16, Mapa de cobertura §20 e mais 20
- SC 1.4.12citado em 4 seçõesMapa de cobertura §cabeçalho, Mapa de cobertura §22, Componentes §cabeçalho, Componentes §72
- SC 2.1.1citado em 7 seçõesMapa de cobertura §22, Componentes §cabeçalho, Componentes §49, Componentes §57 e mais 3
- SC 2.1.2citado em 1 seçãoComponentes §68
- SC 2.2.1citado em 1 seçãoComponentes §66
- SC 2.4.3citado em 1 seçãoComponentes §62
- SC 2.4.11citado em 1 seçãoComponentes §49
- SC 2.5.7citado em 12 seçõesComponentes §cabeçalho, Componentes §06, Componentes §48, Componentes §49 e mais 8
- SC 2.5.8citado em 8 seçõesMapa de cobertura §22, Componentes §cabeçalho, Componentes §51, Componentes §65 e mais 4
- SC 3.2.2citado em 1 seçãoComponentes §67
- SC 3.3.1citado em 1 seçãoComponentes §59
- SC 3.3.2citado em 1 seçãoComponentes §61
- SC 3.3.7citado em 3 seçõesComponentes §02, Componentes §61, Componentes §83
- SC 3.3.8citado em 4 seçõesMapa de cobertura §04, Componentes §cabeçalho, Componentes §03, Componentes §66
- SC 4.1.2citado em 7 seçõesMapa de cobertura §22, Componentes §58, Componentes §62, Componentes §64 e mais 3
- SC 4.1.3citado em 4 seçõesComponentes §65, Componentes §66, Componentes §67, Componentes §68
Os contratos
<button type="button|submit"> real (nunca div clicável) · foco visível sempre · aria-busy no loading · aria-label em icon-only · contraste do rótulo AA em todas as variantes/estados (auditado nos tokens: primary 4.60:1, destructive 5.19:1) · não comunicar estado só por cor (disabled também muda cursor e remove hover; loading tem spinner).
Aplicar SEMPRE que um componente tiver níveis de ênfase; nasceu da revisão do botão (v0.1→v0.2) e vira padrão dos blocos seguintes:
- Teste do container 3:1 (WCAG 1.4.11): o elemento que identifica o controle (preenchimento OU borda) precisa de contraste ≥3:1 contra a superfície. Medir, não estimar.
- Teste da escada de mecanismos: entre níveis adjacentes, muda o MECANISMO (fill → tint+borda → borda → texto), nunca apenas o tom.
- Teste do aperto de olhos / escala de cinza: renderizar o conjunto em grayscale — os níveis continuam distinguíveis? (Remove a muleta do matiz; simula baixa visão e daltonismo.)
- Teste do par: o par mais comum (alta + média ênfase) lado a lado — a hierarquia é óbvia em 1 segundo, à distância de braço?
- Regra do um: 1 alta ênfase por vista; grupos com 1 primário + N iguais de ênfase menor; >3 ações → ghost.
- Teste do estado atual (toggles): alguém que chega AGORA na tela sabe se está ligado ou desligado sem clicar? Se precisa clicar pra descobrir (o "teste WebEx"), reprovou — aplicar a regra dos 3 sinais (§1.5.1). Método de pesquisa (v0.9, calibrado com o Rafael em 2026-08-02 sobre o histórico de 5 itens): todo item roda NO MÍNIMO 3 rodadas de pesquisa, sempre sequenciais e encadeadas — cada rodada abre declarando as pontas soltas que herda da anterior e busca APENAS o que a base acumulada ainda não responde (decisões já estáveis não se re-pesquisam). Arco típico observado no histórico: rodada 1 = canon dos design systems (~60–70% das decisões); rodada 2 = prática das grandes empresas e do mercado (confirmações + ~20%); rodada 3 = normas e fora-do-circuito (menor volume, maior gravidade — foi onde entraram WCAG 3.3.8/NIST, ISO 4217, APG spinbutton, WCAG 2.5.7). Rodada extra (4ª+) por gatilho, nunca por rito: crítica do Rafael expondo lacuna (caso 0800/EUA do form-field), decisões instáveis após 3 rodadas, ou domínio de alto risco (dinheiro, dados pessoais, segurança). Uma rodada que não herda pergunta aberta é redundante e não deve acontecer.
§1.13 — Protocolo de live region SEED (v0.12, transversal): todo anúncio dinâmico a leitores de tela segue UM padrão (nasceu na busca D4, confirmado no textarea F3, agora governa combobox, upload e switch async): região role="status" (polite) + aria-atomic="true", visualmente oculta e separada do nó visual (canais independentes — os 4 bugs do GOV.UK vieram de misturar); anúncio com debounce ~1s (fala na pausa, não a cada evento); frase completa e específica ("14 resultados para 'inversor'", "proposta.pdf enviado"), nunca fragmento; erro que exige ação imediata usa role="alert"; uma live region por componente. Consumidores: §4, §6, §8 (pending), §11, §14.
Nota 1.13-b (v0.15 — emenda, dois regimes de anúncio): o §1.13 nasceu para atualizações contínuas (contagem de busca a cada tecla), onde anunciar cada evento defasaria o visual — daí os canais separados + debounce (D4-b). Mensagens discretas (toast, alerta injetado, banner dinâmico) são conteúdo novo inserido UMA vez: não há defasagem possível, e o padrão universal correto (Spectrum, React Aria, Radix, Base Web, gov.br) é o próprio nó visível ser a live region, injetado já populado. Regra de implementação que a suite cobra nos dois regimes: o container com role precisa existir no DOM ANTES do conteúdo ser inserido — inserir container e conteúdo juntos faz leitores de tela perderem o anúncio (WCAG 4.1.3, técnica documentada). Sem esta nota, a validação executada do Bloco 3 reprovaria o padrão correto por leitura literal do §1.13. Descartado: exigir canal separado também para mensagens discretas (duplicaria o DOM sem ganho e contraria todo o mercado verificado).
- Teste do polegar (v0.7 — mobile): renderizar o componente em viewport de 360px (Android BR mais comum) com teclado virtual aberto (altura útil ~450px): todos os alvos ≥44px ou com hit-area estendida documentada; nada essencial coberto pelo teclado; ordem de leitura íntegra em coluna única; texto do campo ≥16px (anti-zoom iOS); zoom 200% não quebra (WCAG 1.4.4). O preview traz a faixa 360px pré-renderizada, como a faixa grayscale.
O preview traz a faixa em escala de cinza pré-renderizada para o teste 3 ser feito a olho.
<label for> sempre (nunca div-rotulada) · aria-describedby na ordem erro ANTES da ajuda ("err-id help-id" — AUI: o erro é anunciado primeiro) · aria-invalid só após validação real, NUNCA no estado inicial (W3C ARIA21), e SEMPRE com valor explícito "true" — nunca toggleAttribute, que grava o atributo com valor vazio: [aria-invalid="true"] do CSS não casa e a borda de erro some (bug flagrado na validação executada de 2026-08-03) · mensagem de erro com prefixo "Erro:" visually-hidden · erro dinâmico anunciado (região aria-live="assertive" ou role="alert") · ícones de estado com par textual (1.4.1) · foco: anel, nunca borda engrossada · contraste: TODOS os pares da §2.5 medidos ≥4.5:1 texto e ≥3:1 não-texto · zoom 400%: layout de coluna única resiste · toque ≥44px no md.
| # | Decisão | Porquê | Descartado |
|---|---|---|---|
| MK1 | Quatro tipos de página, cada um com contrato próprio: institucional (quem somos, carreira) · serviço (autônoma, ver MK8) · conteúdo (artigo, vídeo, case) · utilitária (contato, obrigado, erro, legal) | O site atual tem 22 páginas sem tipagem nenhuma — mesmo esqueleto e mesmo título para todas, o que é a causa de o título genérico nunca ter incomodado ninguém: não havia onde declarar outro | Contrato único para tudo (produziu exatamente o site atual) · tipagem por assunto em vez de por comportamento (assunto muda, comportamento não) |
| MK2 | Toda página declara: um <h1> único e REAL · <title> próprio · meta descrição própria · URL canônica · lang="pt-BR" |
Linha de base: 6 de 22 páginas sem <h1> real, e nenhuma com título ou descrição próprios. Classe de defeito a evitar: capacidade construída e nunca ligada — a classe SetSeo do site atual estava pronta e nunca foi instanciada, o que é pior que não existir, porque parece resolvido |
<h1> simulado por classe CSS (quebra 1.3.1 e a hierarquia de leitura) · título por template genérico |
| MK3 | Toda <img> de conteúdo tem alt descritivo; decorativa tem alt="" EXPLÍCITO |
419 tags, zero alt, e nem a distinção entre conteúdo e decoração sendo feita. O alt="" explícito é o que diz ao leitor de tela "pule isto de propósito" — omitir o atributo faz o leitor tentar adivinhar pelo nome do arquivo |
alt opcional · alt gerado do nome do arquivo |
| MK4 | Zoom NUNCA é bloqueado. maximum-scale e user-scalable=no são proibidos |
Está no ar hoje em todas as páginas, e o público massivo é celular. Bloquear zoom não é preferência estética: é impedir alguém de ler | — (não há caso legítimo) |
| MK5 | Toda região de navegação é <nav> com nome acessível único, e o nome não contém a palavra "navegação" |
Zero <nav> no site inteiro. Herda o SH2 do §46 sem alteração — a página institucional tem cabeçalho, mega menu, trilha e rodapé, todos navegação |
div/ul genérico · nav anônimo |
| MK6 | Cabeçalho institucional em TRÊS MODOS: completo (institucional e conteúdo: itens de nível 1 expostos + mega menu) · reduzido (serviço: logo + uma ação primária + gatilho do mega menu, sem os itens de nível 1 expostos) · mínimo (utilitária: logo e saída) | Resolve um conflito medido. Página sem barra de navegação converte até 2× mais em teste controlado, e a recomendação de mercado é remover a navegação da página de conversão. Mas a página de serviço precisa pertencer ao site — é isso que evita manter landing pages paralelas, que é o requisito do Rafael. O modo reduzido mantém a navegação a um toque sem competir com o CTA | Remover a navegação (a página deixa de pertencer ao site e você mantém duas coisas) · cabeçalho completo na página de serviço (paga o custo de conversão sem necessidade) |
| MK7 | Mega menu: disclosure de links, nunca role="menu". Máximo 2 colunas, cada grupo com título próprio, imagem opcional por item |
O MN1 já decidiu a fronteira semântica das quatro saídas, e o §46 já consome a saída disclosure. O teto de 2 colunas vem do inventário: o mega menu atual usa 3 grupos, e o único com 2 colunas é "soluções" — que é o caso real de agrupamento duplo (técnicas + fotovoltaico por segmento) | role="menu" (anti-padrão testado, corpus Roselli/APG) · 3+ colunas (o inventário não sustenta a necessidade) |
| MK8 | PÁGINA DE SERVIÇO AUTÔNOMA, em cinco movimentos: conversão → dor → prova → escopo → conversão. Precisa se sustentar para quem cai de anúncio, sem contexto anterior. Um sinal mínimo de prova acompanha o CTA de topo — um número, não parede de logos. O formulário existe UMA VEZ; o CTA do fim é âncora que leva até ele | Sequência definida pelo Rafael. Dor → prova → escopo é PAS (problema, agitação, solução), o mesmo framework que a skill seed-ds-mensagem já usa; a conversão vem antes porque quem chega de tráfego pago já está no meio do PAS — fazer essa pessoa rolar para achar o formulário friciona quem já está pronto, e quem não está rola e encontra a explicação. O sinal de prova no topo tem número: contagem de clientes nomeada acima da dobra produziu +22%, o maior ganho de qualquer formato de prova testado. Formulário único porque duplicá-lo cria duas fontes de verdade, duas medições e o erro clássico de a pessoa preencher o de baixo depois que o de cima já falhou |
Dor → prova → escopo → conversão (minha proposta inicial: friciona quem já decidiu) · dois formulários na mesma página · parede de logos e metodologia no topo |
| MK9 | Variante de PÚBLICO dentro da página de serviço, não página duplicada. No fotovoltaico o recorte é por SEGMENTO — casa, comércio, indústria, área rural | Achado do estudo: o site atual já resolve exatamente assim, com quatro páginas por segmento. Segmento é mais operacional que "B2B/B2C" — o visitante se reconhece em "na minha indústria", não em "sou B2B" | Duas páginas B2B/B2C do mesmo serviço (competem entre si no buscador e dobram a manutenção) · uma página tentando falar com os dois ao mesmo tempo |
| MK10 | Dados estruturados por TIPO de página: Organization na home e na institucional · Service em cada página de serviço, com serviceType, areaServed e provider apontando para a Organization · BreadcrumbList onde há trilha · FAQPage só onde há FAQ real e visível |
O schema.org tem o tipo Service exatamente para isto, e página de serviço é onde a maioria subimplementa. A regra do Google é dura: nunca marcar conteúdo que não está visível ao usuário — marcação fabricada é pior que marcação ausente |
LocalBusiness em toda página (dilui o sinal de entidade) · FAQPage sem FAQ visível na página |
| MK11 | areaServed declara MG, ES e BA. A marcação nunca afirma sede onde não há |
A orientação é explícita: um negócio não deve fingir ter instalações de onde não opera de fato. A SEED atende três estados a partir de duas bases | Uma LocalBusiness por município atendido (fabricaria presença física e é o erro mais comum de marcação local) |
| MK12 | Orçamento de peso por página, MEDIDO no gate: HTML < 50KB · CSS < 60KB · JS < 150KB comprimido · imagens acima da dobra < 200KB. Metas de campo: LCP < 2,5s · INP < 200ms · CLS < 0,1. Toda imagem, vídeo e iframe com dimensão explícita | Orçamento de referência para mobile, e as metas são os limiares de "bom" avaliados no percentil 75 de usuários reais. Dimensão explícita é o que impede o deslocamento de layout. Linha de base: esqueleto fixo de 2,5 MB e páginas de até 8,8 MB de mídia | Orçamento aspiracional sem medição no gate (não é orçamento, é desejo) |
| # | Contrato | Por quê |
|---|---|---|
| BT1 | A barra de controles de todo artefato produzido — bancada OU tela — é fixa no topo: position:sticky; top:0, com faixa de fundo própria e sombra de separação |
Verbatim do gate: "deixe sempre o topo ... sempre travado no topo pra quando rolar a página ele ficar fixo no topo". Bancada é comprida por definição — ela imprime todos os estados. Se o seletor de tema rola para fora, avaliar o tema escuro no meio da página exige subir, trocar, descer e reencontrar o lugar |
| BT2 | Toda bancada oferece seletor de largura de página — Total · 1280 · 1024 · 768 · 390px — limitando o max-width do contêiner de conteúdo |
Avaliar aperto sem redimensionar a janela. Sem isto, "cabe em 390?" vira uma pergunta que só o autor da suíte consegue responder, e o gate humano fica dependente do placar |
| BT3 | O par de estado tem de sobreviver à inversão. Fundo e texto de um mesmo estado medem ≥ 3,00 de contraste nos DOIS temas | É a formulação MEDÍVEL do GI2. O defeito real nunca é "usou primitiva": é primitiva de um lado e semântico do outro — o par se separa quando um dos dois inverte e o outro não |
| BT4 | Primitiva de rampa em superfície de marca (herói, trilho, selo) é tolerada — desde que fundo E texto sejam do mesmo par fixo, e o número seja impresso no placar | Superfície de marca é justamente aquilo que não deve inverter. Proibir a primitiva ali seria proibir a marca. Mas tolerar em silêncio faz o achado sumir: o placar imprime primitiva só em superfície de marca com o pior par medido |
| BT5 | Atributo de CONTRATO não se traduz — data-theme="dark", não data-tema="escuro" |
Regra já escrita em §63.7, repetida aqui porque é da mesma fronteira. O idioma do projeto é pt-BR; o gancho do gêmeo de tokens não é escolha nossa |
| BT6 | Medição de tema roda em página recém-carregada, e a suíte declara isso | Número que depende de quais guardas rodaram antes não é medição, é resíduo (a prova está em §64.4) |
| BT7 | Nenhuma bancada rola na horizontal a 320px. O que pode rolar é a parte que exige layout bidimensional — tabela de dados, grade de glifos —, e só dentro de uma região focável e rotulada (role="region" + tabindex="0" + aria-label) |
SC 1.4.10 Reflow (AA). A norma proíbe a página rolar nas duas direções e abre exceção nomeada para "partes do conteúdo que exigem layout bidimensional". Mas região rolável sem foco de teclado não se rola sem mouse, e sem nome a AT anuncia uma região anônima. Nasceu no rito de promoção de 2026-08-17: as quatro bancadas novas transbordavam a 320px, e três delas também a 390 ou 768 — ninguém tinha medido abaixo de 1200 |
SUPERSEDE FORMAL — o estado de VERIFICAÇÃO do BT1 e do BT2 (acrescentado em 2026-08-20, décima parte)#
Esta subseção define os contratos; ela não os declara verificados. O estado de verificação de cada um, em 2026-08-20, é:
· BT1 — VERIFICADO POR INSTRUMENTO.
validacao/guarda-barra-fixa.mjs, de alvo de pasta, mede as quatro cláusulas BT1-a…d em todo.htmldo diretório: 33 PASS · 0 FAIL · 18 [n/a] a 1440×900 e a 320×900. A especificação completa, o censo dos onze reprovados com número, o conserto canônico e a pasta de provarender-audit/prova-bt1estão em §74 — que SUPERSEDE qualquer afirmação anterior de que a pendência BT-P2 estivesse fechada. A BT-P2 havia sido declarada fechada em 2026-08-17 sobre uma lista escrita à mão de dez artefatos, num acervo de trinta e quatro; onze reprovavam.· BT2 — NÃO VERIFICADO. Nenhum instrumento mede se a bancada oferece seletor de largura de página. A
guarda-barra-fixa.mjsdeclara explicitamente não julgar a composição da barra. Fica como pendência BT-P3 (§74.9). Este parágrafo existe para que "BT2 está escrito" nunca volte a ser lido como "BT2 está cumprido" — que é exatamente o erro que a BT-P2 cometeu.