Acessibilidade
WCAG 2.2 AA é o piso — e o canon diz onde
Os 25 critérios de sucesso da WCAG que o canon cita, cada um com as seções onde aparece (medido nas 385 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 do canon com procedência; o Markdown desta página os traz byte a byte.
No Brasil, o mesmo piso tem norma própria: a ABNT NBR 17225:2025 — Acessibilidade em conteúdo e aplicações web — Requisitos, 1ª edição de 11/03/2025 — adota os patamares da WCAG 2.2, e a "conformidade regular" dela equivale ao nível AA. O art. 63 da Lei nº 13.146/2015 (LBI) obriga a acessibilidade nos sítios de empresas com sede no país. Por isso o canon cita as duas: a WCAG como referência técnica, a NBR 17225 como a norma que se cobra aqui. Edição conferida na ficha da ABNT em 2026-09-10.
O símbolo que a lei exige: a Lei 13.146/2015 (LBI), art. 63, § 1º, manda que "os sítios devem conter símbolo de acessibilidade em destaque" (texto lido no Planalto em 2026-09-15). Por isso toda página deste site abre com a barra de acesso rápido, no topo, com o Símbolo Internacional de Acesso — o desenho de 1968 (ISO 7001 PI PF 006; ABNT NBR 9050:2020, item 5.3.2; Anexo da Lei 7.405/1985), que continua sendo o único reconhecido no Brasil: a Lei 15.459, de 7/7/2026, tentou substituí-lo e o desenho novo foi vetado (Mensagem 586/2026). O vetor usado vem da Wikimedia Commons, em domínio público, reproduzido sem modificação e com procedência registrada em 03-assets/acessibilidade/; será trocado pelo vetor oficial da ISO 7001 quando adquirido.
Atalhos de navegação
A barra de acesso rápido no topo de cada página (padrão eMAG 3.1, itens 4.1 e 4.3) traz quatro links, sempre os mesmos: ir para o conteúdo · ir para o menu · abrir a busca · ir para o rodapé. Os links são visíveis e se alcançam com a tecla Tab; não há tecla de atalho (accesskey), porque a combinação muda por navegador e pode colidir com o leitor de tela (MDN e WebAIM, lidos em 2026-10-10 — a casa tirou o accesskey que tinha). O primeiro elemento focável de toda página continua sendo o link "Ir ao conteúdo" (contrato SH8 do §46), e o tema claro/escuro é o botão ◐ do cabeçalho.
Critérios WCAG citados no canon
- SC 1.2.1citado em 1 seçãoComponentes §94
- SC 1.3.1citado em 2 seçõesComponentes §h67, Componentes §68
- SC 1.3.5citado em 3 seçõesComponentes §61, Componentes §h61, Componentes §66
- SC 1.4.1citado em 3 seçõesComponentes §65, Componentes §90, Componentes §94
- SC 1.4.3citado em 14 seçõesMapa de cobertura §17, Mapa de cobertura §18, Mapa de cobertura §22, Componentes §changelog e mais 10
- SC 1.4.10citado em 8 seçõesComponentes §changelog, Componentes §43-b, Componentes §57, Componentes §58 e mais 4
- SC 1.4.11citado em 31 seçõesMapa de cobertura §11, Mapa de cobertura §15, Mapa de cobertura §16, Mapa de cobertura §20 e mais 27
- SC 1.4.12citado em 6 seçõesMapa de cobertura §cabeçalho, Mapa de cobertura §22, Componentes §changelog, Componentes §72 e mais 2
- SC 1.4.13citado em 1 seçãoComponentes §46
- SC 2.1.1citado em 8 seçõesMapa de cobertura §22, Componentes §changelog, Componentes §49, Componentes §h57 e mais 4
- SC 2.1.2citado em 1 seçãoComponentes §68
- SC 2.1.4citado em 1 seçãoComponentes §47
- SC 2.2.1citado em 1 seçãoComponentes §66
- SC 2.4.3citado em 1 seçãoComponentes §h62
- SC 2.4.11citado em 4 seçõesComponentes §31, Componentes §46, Componentes §49, Componentes §h49
- SC 2.5.5citado em 3 seçõesComponentes §changelog, Componentes §00, Boas práticas §18
- SC 2.5.7citado em 16 seçõesComponentes §changelog, Componentes §06, Componentes §h06, Componentes §48 e mais 12
- SC 2.5.8citado em 14 seçõesMapa de cobertura §22, Componentes §changelog, Componentes §00, Componentes §01 e mais 10
- SC 3.2.2citado em 1 seçãoComponentes §67
- SC 3.3.1citado em 1 seçãoComponentes §59
- SC 3.3.2citado em 2 seçõesComponentes §61, Componentes §h61
- SC 3.3.7citado em 3 seçõesComponentes §02, Componentes §61, Componentes §83
- SC 3.3.8citado em 5 seçõesMapa de cobertura §04, Componentes §changelog, Componentes §03, Componentes §h03 e mais 1
- SC 4.1.2citado em 7 seçõesMapa de cobertura §22, Componentes §h58, Componentes §h62, 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.
06-validacao/guardas/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 — VERIFICADO POR INSTRUMENTO (SUPERSEDE FORMAL de 2026-09-21, pendência
V-PR15). O texto que vigorava aqui era: "BT2 — NÃO VERIFICADO. Nenhum instrumento mede se a bancada oferece seletor de largura de página. Aguarda-barra-fixa.mjsdeclara explicitamente não julgar a composição da barra. Fica como pendência BT-P3 (§74.9)." Fica registrado e deixa de valer. O número apareceu, e ele é pior do que se supunha: medido em 2026-09-21, ANTES de aplicar, dos 26 artefatos com barra de provas apenas 8 tinham o<select>das cinco larguras do contrato; 4 tinham três botões com larguras que NÃO são as do contrato (360·768·1180); e 14 não tinham controle de largura NENHUM — inclusive a bancadabanco-dominio, que o BT2 obriga. Depois da convergência da gramática única da barra: 26 de 26 com o<select>das cinco larguras do contrato (Total · 1280 · 1024 · 768 · 390). O instrumento que passa a manter isso medido écontroles_bloco.conferir(), no ato de GERAR: ele aborta a geração se a barra não tiver o seletor ou se faltar qualquer uma das cinco opções, e roda sobre os 27 geradores dentro do rito (06-validacao/fechar.sh, etapa--checar). Aguarda-barra-fixa.mjscontinua não julgando a composição da barra — quem julga é o módulo único, no ato de emitir. Atualização de 2026-09-24 (Grupo 9, MANIFESTO §245): obanco-dataviz-dpentrou na gramática pelo gerador (BT-P8) — 27 de 27 barras emitidas pelo módulo. E o BT2 passou a ser medido também no ARTEFATO, como o Chrome o resolve, no rito:guarda-barra-gramatica.mjs(contrato G1–G5,PR-P26) confere o nome acessível pela árvore de acessibilidade, otyperesolvido e o seletor presente E operável (escolher 390 põemax-width: 390pxem algum elemento) — oconferir()prova o que se emite, a guarda prova o que roda. O denominador verdadeiro é 39, não 27: a BT1 conta 39 artefatos com barra de provas; 12 estão fora da gramática única (10 bancadas escritas à mão comid="seed-barra-provas", sem gerador, ebanco-dataviz-dg/-dmcom a barra antiga) —BT-P10. O parágrafo anterior existia para que "BT2 está escrito" nunca fosse lido como "BT2 está cumprido". Ele cumpriu o papel: quando o número finalmente foi medido, 14 de 26 estavam fora do contrato — exatamente o erro que a BT-P2 tinha cometido, e que este parágrafo evitou repetir.