Botão ou link? Botão faz uma ação, link leva a um lugar
Escolha entre a e button pelo que o elemento faz, troque divs clicáveis e href="#" pelo elemento certo e teste com Tab, Enter, Espaço e clique do meio.
O "Entrar" do cabeçalho é uma div com onclick. O Tab passa direto por ela, o leitor de tela anuncia só o texto, sem dizer que é clicável, e Enter não faz nada. Ao lado, o menu abre com um <a href="#"> que joga a página para o topo quando o script demora a carregar. E o "Ver planos" é um botão que muda o location por JavaScript, então ninguém consegue abri-lo numa aba nova. A resposta curta: se leva para outra página ou outro ponto da página, é <a href>; se faz alguma coisa ali mesmo, é <button>. O visual não decide nada: um link pode parecer botão e vice-versa, com CSS.
Escolhido o elemento certo, o navegador entrega de graça o foco, as teclas, o anúncio para tecnologia assistiva e comportamentos que as pessoas esperam, como abrir em nova aba.
O que cada elemento traz pronto
Um link com href recebe foco com Tab, é ativado com Enter, é anunciado como "link", aparece na lista de links do leitor de tela, pode ser aberto em nova aba com clique do meio ou Ctrl + clique, tem "copiar endereço do link" no menu de contexto, entra no histórico e é seguido por buscadores.
Um botão recebe foco com Tab, é ativado com Enter e com Espaço, é anunciado como "botão", aceita o atributo disabled (que tira o foco e bloqueia a ação) e, dentro de um formulário, envia o formulário por padrão.
Uma div com clique não traz nada disso. Para chegar perto de um botão, ela precisa de role="button", tabindex="0", tratamento de Enter e Espaço no teclado, estilo de foco e lógica própria de desabilitado. É reimplementar, com bugs, o que o <button> já faz.
A troca no sentido contrário também cobra. Um botão que navega por JavaScript perde nova aba, copiar endereço e indexação. Um link sem destino real, usado para abrir menu, é anunciado como link, faz a pessoa esperar uma navegação que não vem e, com href="#", rola a página para o topo se o script falhar.
Como escolher e corrigir
- Pergunte o que acontece ao ativar. A URL muda ou a pessoa vai para outro trecho? Link. Algo muda na própria tela, um envio acontece, um painel abre? Botão.
- Troque toda
divespanclicáveis por<button type="button">ou<a href>. - Troque
href="#"ehref="javascript:void(0)"por botões, quando não há destino. - Troque botões que mudam o
locationpor links com o endereço real. - Declare
type="button"em todo botão dentro de formulário que não deve enviá-lo. - Dê nome visível ao elemento, o mesmo que será anunciado. Se for só ícone, veja ícone no botão precisa de nome.
- Ajuste o visual com CSS depois, sem mexer na escolha do elemento.
O cabeçalho antes
<div class="btn" onclick="abrirLogin()">Entrar</div>
<a href="#" onclick="alternarMenu()">Menu</a>
<button onclick="location.href='/planos'">Ver planos</button>
O cabeçalho depois
<a class="btn" href="/entrar">Entrar</a>
<button type="button" aria-expanded="false" aria-controls="menu-principal">Menu</button>
<ul id="menu-principal" hidden>
<li><a href="/blog">Blog</a></li>
<li><a href="/contato">Contato</a></li>
</ul>
<a class="btn" href="/planos">Ver planos</a>
const botaoMenu = document.querySelector('[aria-controls="menu-principal"]');
const menu = document.getElementById('menu-principal');
botaoMenu.addEventListener('click', () => {
const aberto = botaoMenu.getAttribute('aria-expanded') === 'true';
botaoMenu.setAttribute('aria-expanded', String(!aberto));
menu.hidden = aberto;
});
"Entrar" virou link porque leva à página de login, que tem endereço próprio. Se o login abrisse num diálogo na mesma página, seria botão. O menu virou botão com aria-expanded, que diz ao leitor de tela se ele está aberto ou fechado. O evento click do botão dispara com mouse, Enter e Espaço, então não há tratamento de teclado para escrever. "Ver planos" virou link, e agora abre em nova aba, aparece para buscadores e funciona mesmo se o JavaScript falhar.
Casos que confundem
- "Baixar relatório": se é um arquivo com endereço, link (com o atributo
download, se quiser). Se o arquivo é gerado por uma ação que precisa de POST, botão num formulário. - Abas: trocar o painel visível sem mudar de página é ação, então são botões, com o padrão de abas da ARIA. Se cada aba tem URL própria, é navegação com links.
- "Excluir" numa lista: sempre botão, nunca link com GET. Link é seguido por pré-carregamento e robôs; uma exclusão por GET pode acontecer sem ninguém clicar.
- Componentes de rota em React, Vue ou Angular: o componente de link do roteador gera um
<a href>de verdade. Use-o para navegar, em vez de um botão que chama a navegação por código.
Os eventos de ponteiro e teclado por trás disso estão em o clique no ponteiro não cobre o teclado, e o caso de bibliotecas que entregam o visual sem a semântica, em componente pronto não é acessível por padrão.
Erros comuns
divouspancomonclick. Não recebe foco, não responde a teclado e não é anunciada como controle.- Link com
href="#"para abrir menu. Anunciado como link, rola para o topo quando o script falha e polui o histórico. - Botão que navega por JavaScript. Sem nova aba, sem copiar endereço, sem indexação, sem funcionar quando o script quebra.
- Botão sem
typedentro de formulário. O padrão ésubmit: o "Mostrar senha" envia o formulário. - Exclusão por link com GET. Pré-carregamento, robôs e extensões podem disparar a ação.
- Escolher pelo visual. "Parece botão" não é critério; o critério é o que acontece ao ativar.
Checklist
- Nenhuma
divouspantem manipulador de clique como único jeito de acionar algo. - Todo link tem
hrefcom destino real. - Toda navegação pode ser aberta em nova aba com clique do meio.
- Todo botão dentro de formulário tem
typedeclarado. - Botões que abrem e fecham coisas usam
aria-expanded. - Ações destrutivas são botões, nunca links com GET.
- Tudo funciona com Tab, Enter e, nos botões, Espaço.
Desconecte o mouse e percorra o cabeçalho e o rodapé do seu site só com Tab, Enter e Espaço: algum controle foi pulado, ou exigiu um script extra só para receber foco?
Referência: https://www.w3.org/WAI/ARIA/apg/patterns/button/