Acessibilidade além da sintaxe

Regras confirmam que a página tem título, o link tem texto e a imagem tem alt. Não percebem que “Untitled document”, “Click here” ou alt="img-1" não dizem nada. O Rampa roda o axe-core, pergunta a um modelo só o que as regras não decidem e só mantém um achado quando a evidência confere na página.

Código aberto, licença MIT. Funciona com modelo local ou com os grandes provedores.

Ilustração: uma escada na entrada de um prédio vira uma rampa conforme a página rola. A rampa sobe 1 a cada 12 de comprimento, a inclinação máxima permitida para cadeira de rodas.

Regras veem o atributo, não o que ele diz

Em fevereiro de 2026, 95,9% das home pages do milhão de sites mais acessados tinham falhas WCAG que ferramentas automáticas detectam, segundo o WebAIM Million. Essas são as fáceis.

Um título “Untitled document”, um “Click here” solto, um título “Section 2” em cima das avaliações, um alt que nomeia a coisa errada: verificadores de regras aprovam todos, porque o elemento está lá.

Ilustração de um cachorro marrom com coleira vermelha e medalha amarela.<img src="dog.svg" alt="img-1">
Uma página de exemplo publica esta ilustração com alt="img-1".
axe-core

Aprova

A imagem tem texto alternativo. regra image-alt

Rampa

Reprova

O texto alternativo "img-1" é um nome de arquivo ou um placeholder.

Evidência, encontrada na página: img-1

Sugestão no idioma da página: alt="Ilustração de um cachorro marrom com coleira vermelha"

A sugestão é para uma pessoa revisar. O Rampa nunca reescreve a sua página.

Mais cinco que passam em todas as regras

Na páginaaxe-coreRampa
WCAG 2.4.2<title>Untitled document</title>
aprovaO título da página “Untitled document” não diz nada sobre esta página.
WCAG 2.4.4<a href="shipping.html">Click here</a>
aprovaO texto do link “Click here” não diz para onde o link leva, e nada ao redor diz.
WCAG 2.4.6<h2>Section 2</h2>
aprovaO título “Section 2” não diz nada sobre o conteúdo abaixo dele.
WCAG 2.4.6<label for="email">Field 1</label>
aprovaO rótulo “Field 1” não diz o que preencher.
WCAG 3.1.1<html lang="en">numa página em português
aprovaA página está marcada como lang="en", mas a maior parte do texto está em português (pt).

Cada linha é um achado real dos exemplos da loja e da feira no repositório, com evidência e patch.

Um critério por vez, e só com evidência

Toda execução passa por cinco etapas. O modelo só vê o que as etapas anteriores não resolveram.

  1. Coletar

    A página vira um snapshot de acessibilidade normalizado: papéis, nomes, idiomas, estados e cada imagem como foi renderizada.

  2. Regras primeiro

    O axe-core roda na página. O que as regras decidem vai direto para o relatório, sem modelo nenhum.

  3. Julgar o resíduo

    O que as regras não decidem vai para um modelo: um prompt por critério WCAG, o menor contexto que responde à pergunta e uma resposta tipada.

  4. Conferir a evidência

    Toda alegação precisa citar um nó que existe e um texto que está de fato nele. A que não confere é descartada e contada, nunca mostrada.

  5. Relatar a cobertura

    Os achados vêm com patch, e o relatório lista o que ninguém verificou. Nunca declara uma página acessível.

Dos 50 critérios de sucesso da WCAG 2.1 nos níveis A e AA, uma execução no exemplo da loja verifica 12, julga 6 deles com evidência e deixa 38 para pessoas.

Rode numa página

Uma página de loja com nove problemas. O axe-core pega a logo sem texto alternativo; os outros oito passam em todas as regras: três textos alternativos, o título da página, um link, um título de seção, um rótulo de campo e uma citação no idioma errado. Esta é a saída real de uma execução com modelo local.

rampa check examples/store/before.html

examples/store/before.html
Superfície: web · axe-core 4.14.0 · Modelo: ollama:gemma4:12b

WCAG 1.1.1 (A) — Conteúdo não textual
  ✗ html > body > header > img
    Imagens devem ter texto alternativo
    alta · regra image-alt
  ✗ html > body > main > div > figure:nth-of-type(1) > img
    O texto alternativo "IMG_2034.jpg" é um nome de arquivo ou um placeholder.
    Evidência: "IMG_2034.jpg"
    Patch:
      - <img src="mug.svg" alt="IMG_2034.jpg">
      + <img src="mug.svg" alt="Blue mug with steam">
    confiança alta · 1/1 rodadas · evidência verificada · id 2c1d064d9cc7
  ✗ html > body > main > div > figure:nth-of-type(2) > img
    O texto alternativo "Ceramic coffee mug" descreve algo que a imagem não mostra.
    Evidência: "Ceramic coffee mug"
    Patch:
      - <img src="umbrella.svg" alt="Ceramic coffee mug">
      + <img src="umbrella.svg" alt="Pink umbrella in the rain">
    confiança alta · 1/1 rodadas · evidência verificada · id 55249d898b55
  ✗ html > body > main > div > figure:nth-of-type(3) > img
    O texto alternativo "product" é genérico demais para descrever a imagem.
    Evidência: "product"
    Patch:
      - <img src="plant.svg" alt="product">
      + <img src="plant.svg" alt="Potted plant">
    confiança alta · 1/1 rodadas · evidência verificada · id a2e0ae93ad7c

WCAG 2.4.2 (A) — Página com título
  ✗ html
    O título da página "Untitled document" não diz nada sobre esta página.
    Evidência: "Untitled document"
    Patch:
      - <title>Untitled document</title>
      + <title>Corner Store - New Arrivals</title>
    confiança alta · 1/1 rodadas · evidência verificada · id a7fe7f746d48

WCAG 2.4.4 (A) — Finalidade do link (em contexto)
  ✗ html > body > main > p:nth-of-type(2) > a
    O texto do link "Click here" não diz para onde o link leva, e nada ao redor diz.
    Evidência: "Click here"
    Patch:
      - <a href="shipping.html">Click here</a>
      + <a href="shipping.html">View shipping information</a>
    confiança alta · 1/1 rodadas · evidência verificada · id 1af8a73e209d

WCAG 2.4.6 (AA) — Cabeçalhos e rótulos
  ✗ html > body > main > h2:nth-of-type(1)
    O título "Section 2" não diz nada sobre o conteúdo abaixo dele.
    Evidência: "Section 2"
    Patch:
      - <h2>Section 2</h2>
      + <h2>Customer Reviews</h2>
    confiança alta · 1/1 rodadas · evidência verificada · id ef7716588da6
  ✗ #email
    O rótulo "Field 1" não diz o que preencher.
    Evidência: "Field 1"
    Patch:
      - <label for="email">Field 1</label>
      + <label for="email">Email address</label>
    confiança alta · 1/1 rodadas · evidência verificada · id 064fe292df3a

WCAG 3.1.2 (AA) — Idioma de partes
  ✗ html > body > main > blockquote:nth-of-type(1)
    Marcado como lang="es", mas o texto está em holandês (nl).
    Evidência: "Prachtige mok, de koffie blijft lang warm. Ik koop er zeker nog een!"
    Patch:
      - <blockquote lang="es">
      + <blockquote lang="nl">
    confiança alta · 1/1 rodadas · evidência verificada · id ade39251a2e7

Chamadas ao modelo: 13 novas, 0 do cache · 7,1 mil tokens de entrada / 0,9 mil de saída · modelo local, sem custo de API

Cobertura desta execução
  Verificado pelo axe-core (parcial):  1.1.1, 1.3.1, 1.3.5, 1.4.3, 2.4.1, 2.4.2, 2.4.4, 3.1.1, 3.1.2, 3.3.2, 4.1.2
  Julgado com evidência verificada:    1.1.1, 2.4.2, 2.4.4, 2.4.6, 3.1.1, 3.1.2
  Não verificado automaticamente:      38 de 50 critérios WCAG 2.1 A/AA (--verbose lista)
Este relatório não declara a página acessível.
O que não foi verificado exige revisão manual e teste com pessoas.

Medido com os casos de teste do próprio W3C

O W3C publica casos de teste com o resultado esperado para cada regra de acessibilidade. O rampa eval roda o axe-core sozinho e o axe-core com o Rampa nas mesmas páginas, nos seis critérios.

Precisão, recall e F1 por conjunto, com o Gemma 4 12B numa GPU local.
ConjuntoTestaCasosaxe-core, precisão / recallRampa, precisão / recall / F1
1.1.1, imagem tem nome (ACT 23a2a8)sintaxe181,00 / 1,001,00 / 1,00 / 1,00
1.1.1, nome descreve a imagem (ACT qt1vmo)significado16não definida, nada apontado / 0,001,00 / 1,00 / 1,00
2.4.2, página tem título (ACT 2779a5)sintaxe121,00 / 1,000,50 / 1,00 / 0,67
2.4.2, título descreve a página (ACT c4a8a4)significado6não definida, nada apontado / 0,001,00 / 1,00 / 1,00
2.4.4, link tem nome (ACT c487ae)sintaxe281,00 / 1,000,92 / 1,00 / 0,96
2.4.4, link é descritivo no contexto (ACT 5effbb)significado18não definida, nada apontado / 0,000,67 / 1,00 / 0,80
2.4.6, título de seção é descritivo (ACT b49b2e)significado12não definida, nada apontado / 0,000,80 / 1,00 / 0,89
2.4.6, rótulo de campo é descritivo (ACT cc0f0a)significado16não definida, nada apontado / 0,001,00 / 0,83 / 0,91
3.1.1, página tem lang válido (ACT b5c3f8, bf051a)sintaxe111,00 / 1,001,00 / 1,00 / 1,00
3.1.1, lang confere com a página (ACT ucwvc8)significado140,00 / 0,000,50 / 1,00 / 0,67
3.1.2, lang válido em trechos (ACT de46e4)sintaxe191,00 / 1,001,00 / 1,00 / 1,00
3.1.2, lang confere com o texto (ACT off6ek)significado13não definida, nada apontado / 0,000,80 / 1,00 / 0,89

Quebre uma página de propósito e veja quem percebe

Cada par é uma página de teste que passa e uma cópia quebrada de propósito. Um alt vira img-1, o título vira “Welcome”, os títulos de seção viram “Part 1” e os rótulos “Entry 1”, um lang vira outro idioma válido. Nenhuma dessas palavras aparece nos prompts. Quem dá o mesmo veredito às duas versões não está julgando.

O método vem de López-Gil e Pereira (2025).

Pares separados, pelo que foi quebrado. Um quadrado cheio é um par em que as duas versões receberam vereditos diferentes.
Quebrado de propósitoParesaxe-core, separouRampa, separou
Alt de imagem202
Título da página303
Títulos e rótulos908
lang da página404
lang em trechos504
Todos os pares23021

Numa GPU local, a avaliação não custa nada. Com o gpt-6-luna, os 36 julgamentos de 1.1.1 e 3.1.2 levaram 29 segundos e cerca de meio centavo de dólar, e duas rodadas com chamadas novas deram o mesmo veredito em todos os casos.

As amostras são pequenas e cada conjunto rodou uma vez. Os prompts dos quatro critérios novos foram ajustados depois de ler os erros deles nestes casos, então esses números são otimistas até páginas novas confirmarem. Duas precisões baixas são da contagem: o 2779a5 só pede que o título exista, e as páginas dele se chamam “Title of the page.”; o ucwvc8 conta como falso positivo páginas sem lang válido, que o axe-core reprova.

Método e análise de erros no README

Roda no seu modelo

Escolha o modelo de cada critério com o rampa eval, não pela fama. Toda resposta fica em cache, então a mesma entrada nunca chama um modelo duas vezes.

Na sua máquina

Nada sai do seu computador, e não há custo de API.

  • Ollama
  • LM Studio
  • Qualquer servidor compatível com a OpenAI, como vLLM ou llama.cpp

Com uma chave

Só o que as regras não decidiram sai da sua máquina.

  • OpenAI
  • Anthropic
  • Google Gemini
  • Azure OpenAI
  • Amazon Bedrock
  • Google Vertex AI
  • Mistral AI
  • xAI
  • Groq
  • DeepSeek
  • Together AI
  • Fireworks AI
  • Cerebras
  • OpenRouter
  • Vercel AI Gateway

Escolha por opção, variável de ambiente ou arquivo de configuração:

$ rampa check https://example.com --model ollama:gemma4:12b
$ RAMPA_MODEL=openai:gpt-6-luna rampa check https://example.com
$ rampa models

O que o Rampa não faz

Declarar uma página acessível.
Todo relatório lista o que foi verificado, o que foi julgado e o que ninguém verificou. Passar numa verificação não é o mesmo que ser acessível.
Mostrar o que não consegue provar.
A alegação de um modelo só chega a você se o nó citado existe e o trecho citado está nele.
Afogar você em ruído.
Falso positivo é o que faz equipes desligarem um verificador, então limiar de confiança, dispensas e votação entre rodadas fazem parte do projeto.
Mexer nas suas páginas em produção.
Não é overlay. Roda onde você constrói: na sua máquina e no CI.
Mandar dados para fora.
Não há telemetria. Com modelo local, nada sai do seu computador.
Prender você a um fornecedor.
Use qualquer provedor, e deixe o rampa eval medir em qual modelo confiar para cada critério.
Substituir pessoas.
Verificação automática cobre parte da WCAG. Auditoria e teste com pessoas com deficiência cobrem o resto.

A web hoje, qualquer tela depois

Os critérios leem um snapshot normalizado, nunca o DOM. Uma tela de Android ou iOS vira o mesmo formato, então a verificação do critério 1.1.1 vale igual para alt, contentDescription ou accessibilityLabel. Os coletores de Android e iOS estão no roadmap, e um snapshot exportado pelos testes do seu app já pode ser verificado hoje.

  • WebaltDisponível
  • AndroidcontentDescriptionNo roadmap
  • iOSaccessibilityLabelNo roadmap

Perguntas sobre teste automático de acessibilidade

O axe-core basta para cumprir a WCAG?

Nenhuma ferramenta automática basta. O axe-core acha com segurança o que a marcação prova: alt ausente, link vazio, contraste baixo. Ele não sabe se um alt descreve a imagem ou se um título descreve a seção. O Rampa mantém o axe-core no que as regras decidem e julga parte do resto; a maioria dos critérios da WCAG ainda precisa de pessoas.

IA consegue verificar acessibilidade na web?

Em parte. Um modelo de linguagem julga significado, como se o texto de um link diz para onde ele leva, mas também pode inventar problemas. O Rampa pergunta um critério por vez, com o menor contexto, e descarta toda alegação cuja evidência não está na página. O rampa eval mede o resultado com os casos de teste ACT do W3C.

Quais critérios da WCAG o Rampa verifica?

O axe-core cobre partes de cerca de uma dúzia de critérios WCAG 2.1 A e AA. O Rampa ainda julga seis com evidência: 1.1.1 Conteúdo não textual, 2.4.2 Página com título, 2.4.4 Finalidade do link (em contexto), 2.4.6 Cabeçalhos e rótulos, 3.1.1 Idioma da página e 3.1.2 Idioma de partes.

Minha página sai da minha máquina?

Não com um modelo local pelo Ollama. Com um provedor na nuvem, só vai o que as regras não decidiram, um elemento e seu contexto por pergunta. Não há telemetria.

Qual a diferença entre o Rampa e um overlay de acessibilidade?

Um overlay muda a página no ar para quem visita e deixa o código como estava. O Rampa roda onde você constrói, no terminal ou no CI, e entrega cada achado com um patch para uma pessoa revisar.

Verifique uma página

O Rampa ainda não está no npm. Clone e rode a partir do código, com Node.js 22.12 ou mais novo e Chrome ou Edge.

Com o Ollama rodando, o Rampa escolhe um modelo local sozinho. Senão, coloque uma chave como OPENAI_API_KEY num arquivo .env. O node dist/cli.mjs doctor diz o que falta.

Abrir o repositório
git clone https://github.com/guilhermebsantiago/rampa-cli.git
cd rampa-cli
pnpm install && pnpm build
node dist/cli.mjs check https://example.com