Capítulo 51
DOM, eventos e acessibilidade
6 min de leitura 1.064 palavras 2 gravuras
Este capítulo liga o JavaScript vanilla ao HTML: selecionar, ouvir clique e teclado, e não estragar a acessibilidade. A Oficina Costeira é um coletivo fictício de design em João Pessoa. Este volume tem 52 capítulos. Esta é a 51.
O exemplo está em exemplos/51-dom-eventos-acessibilidade/. Use querySelector e addEventListener. jQuery aparece só como nota de “como era”. Ciclo: ler, copiar, mudar uma coisa, prever, recarregar, conferir. Teste com Tab, não só com o mouse.
O que você vai aprender
- Entender o DOM como o mapa ao vivo do documento.
- Selecionar com
querySelectorequerySelectorAll. - Ouvir eventos com
addEventListener. - Atualizar texto e atributos ARIA no que o script cria.
- Manter a página usável se o script falhar.
- Reconhecer jQuery como atalho antigo, não como exercício.
O DOM é o documento em forma de árvore
O navegador lê o HTML e monta uma árvore de nós: elementos, texto, atributos. Essa árvore é o DOM (modelo de objeto do documento). JavaScript não “pinta a tela” direto. Ele pede nós ao DOM e muda esses nós. A tela acompanha.
document é a raiz. document.querySelector("h1") devolve o primeiro h1 ou null.
const titulo = document.querySelector("h1");
if (titulo) {
console.log(titulo.textContent);
}
Sempre pergunte se o nó existe. Página da agenda não tem data-grafico. O mesmo arquivo JS pode rodar em várias páginas se você proteger com if.
Mude só o seletor para h2. Preveja o primeiro h2 da página, ou null na home se não houver. Recarregue. Confira o console. Volte para o seletor que o exercício usa.
Erro comum: chamar titulo.textContent sem o if. Se titulo é null, o console mostra erro e o restante do script para.
Selecionar como no CSS
querySelector aceita seletor CSS. Um ponto, um #id, um [atributo]. querySelectorAll devolve uma lista. Não é um array completo, mas você percorre com for...of.
const raiz = document.querySelector("[data-grafico]");
const itens = document.querySelectorAll(".card");
for (const card of itens) {
console.log(card.querySelector("h2")?.textContent);
}
O ?. evita erro se o h2 faltar. Mesmo assim, o HTML da Oficina Costeira deve ter o título. Script não desculpa card sem h2.
Não use getElementById e querySelector misturados sem motivo. Um estilo só. Neste volume: querySelector.
Mude só [data-grafico] para div. Preveja o primeiro div da página, que pode ser outro. Recarregue. Confira. Selecionar demais é tão ruim quanto não selecionar. Prefira um atributo de propósito (data-grafico).
Erro comum: seletor de classe sem ponto (card em vez de .card). Isso procura a tag <card>, que não existe.
Eventos: o clique não é o único
Evento é algo que acontece: clique, tecla, envio de formulário, fim do carregamento. addEventListener liga uma função a um evento num nó.
<p>
<button type="button" data-aviso>Lembrar: nada é enviado</button>
</p>
<p id="recado" hidden></p>
const botao = document.querySelector("[data-aviso]");
const recado = document.querySelector("#recado");
if (botao && recado) {
botao.addEventListener("click", function () {
recado.hidden = false;
recado.textContent = "Este formulário da Oficina Costeira não envia dados.";
});
}
O botão é type="button", não submit. Não há rede. O recado vive na página, com texto, não num alert que trava o leitor.
Formulário demonstrativo:
const form = document.querySelector("form");
if (form) {
form.addEventListener("submit", function (evento) {
evento.preventDefault();
});
}
preventDefault cancela o envio. A validação required do HTML ainda pode aparecer antes, no navegador. Você não grava nome nem e-mail.
Mude só o texto do recado. Preveja a frase nova depois do clique. Recarregue. Clique. Confira. Depois tente a mesma ação só com o teclado: Tab até o botão, Enter. Confira de novo.
Erro comum: onclick="..." no HTML. A mistura impede vários ouvintes e suja o documento. Ouça no JS.

ARIA no que o JavaScript cria
ARIA descreve papéis e estados para a tecnologia assistiva. No HTML estático da Oficina Costeira você já usa aria-current, aria-label no nav, skip-link. No JS, se criar um controle, atualize o estado.
Exemplo: um botão que abre um painel fictício de números.
botao.setAttribute("aria-expanded", "true");
painel.hidden = false;
Quando fechar:
botao.setAttribute("aria-expanded", "false");
painel.hidden = true;
Não coloque ARIA de abas num HTML que, sem JS, é só uma lista. Sem script, a lista deve permanecer lista. Com script, se você virar abas, aí sim role, aria-selected e teclas. Este volume não exige o widget de abas. Exige: não mentir o estado, não tirar o foco, não capturar o Tab para sempre.
Foco visível continua no CSS (outline de 3 px). O script não deve fazer outline: none.
Mude só aria-expanded para "true" e esqueça de mostrar o painel. Preveja: o leitor pode anunciar aberto e a tela não mostra. Recarregue. Confira os dois lados. Estado e visual andam juntos.
Erro comum: div clicável com click e sem tabindex, sem papel de botão, sem Enter. Use button.
Infográfico acessível o bastante
O infográfico da Oficina Costeira não pode ser a única leitura. A lista <ul> vem primeiro. O div data-grafico recebe parágrafos com textContent. Se o script falhar, a lista resta.
const raiz = document.querySelector("[data-grafico]");
if (!raiz) {
// A página ainda funciona: a lista HTML abaixo já conta a história.
} else {
const barras = [
{ nome: "Identidade", valor: 8 },
{ nome: "Publicação", valor: 12 },
{ nome: "Oficina", valor: 5 }
];
raiz.replaceChildren();
for (const item of barras) {
const p = document.createElement("p");
p.textContent = item.nome + ": " + item.valor + " trabalhos fictícios";
raiz.append(p);
}
}
Se um dia as barras forem só cor, coloque o número no texto mesmo assim. Cor não é dado para quem não vê a cor.
Mude só os três valores no script e esqueça a lista HTML. Preveja divergência. Recarregue. Confira. Alinhe os dois. Acessibilidade aqui também é consistência.
Erro comum: innerHTML com string montada de dado desconhecido. Neste exercício os números são seus e fictícios, mas o hábito de textContent já evita a porta.
Como era o jQuery (nota curta)
Antigamente muitos tutoriais escreviam $("#recado").text("...") e $(botao).on("click", ...). Isso era jQuery: uma biblioteca que suavizava diferenças entre navegadores e encurtava a seleção.
Hoje querySelector e addEventListener fazem o trabalho deste volume. Você não vai baixar jQuery, não vai ligar CDN, não vai reescrever a Oficina Costeira em $. Se achar um snippet antigo, traduza: $(".card") → document.querySelectorAll(".card").
Não misture os dois. Não é exercício traduzir um plugin de carrossel.
python -m http.server 8769 --bind 127.0.0.1 --directory curso
Abra http://127.0.0.1:8769/exemplos/51-dom-eventos-acessibilidade/. Percorra com Tab. Encerre com Ctrl+C. Não use 0.0.0.0. Não publique. Formulário sem dado real.

Atividades
- O que
querySelectordevolve quando o seletor não acha nada? - Por que o botão de aviso é
type="button"e não umdiv? - O que
preventDefaultnosubmitestá evitando neste volume? - Se você cria um painel, quais dois lados precisam combinar?
- jQuery é obrigatório para ouvir um clique hoje?
Respostas comentadas
null. Por isso oifantes de usar o nó.buttonjá recebe foco, Enter e espaço.divnão é controle.type="button"não envia formulário.- O envio do formulário para uma URL. O exercício não grava e-mail nem mensagem.
- O visual (
hiddenou equivalente) e o estado ARIA (aria-expandedou similar). Um sem o outro mente. - Não.
addEventListener("click", ...)é vanilla e suficiente.
Referências
O arquivo completo
Referência do exemplo 51-dom-eventos-acessibilidade, reproduzida aqui para que você possa conferir sem sair da página.
assets/javascript/simulacao.js
const form=document.querySelector("#simulacao");if(form){form.querySelector("button[type=submit]").disabled=false;form.addEventListener("submit",event=>{event.preventDefault();document.querySelector("#retorno").textContent="Simulação concluída. Nada foi enviado ou guardado.";});}