Âncoras versus botões

Introdução

Por muito tempo desenvolvedores web (inclusive eu) usamos elementos <a>, <div>, <span> e uma série de outros exceto o elemento <button> para criar componentes interativos clicáveis em uma página. Algo semelhante ao mostrado a seguir.

HTML
<a href="#" role="button" onclick="openMenuModal();">Navegação</a>  
Não faça isso

O atributo href definido para o elemento âncora recebe um valor tal que quando o usuário clica o link não acontece a esperada ação de navegação típica de quando se clica um link. Usa-se JavaScript para alterar a ação padrão do evento clique. Mas, existe um elemento HTML apropriado para exercer aquele comportamento que foi simulado com uso da JavaScript. Trata-se do elemento <button> ou mais especificamente o elemento <button type="button">.

HTML
<button type="button" onclick="openMenuModal();">Navegação</button>  

Apesar disto o elemento âncora vem sendo amplamente usado para disparar interações na página, e na maioria dos casos, de forma incorreta. Contudo, convém notar que o botão não é a solução para tudo e eu acredito que em alguns casos o uso da âncora é mais apropriado. Para entendermos o quando e o porquê vamos começar estabelecendo as diferenças entre âncoras e botões.

Diferenças entre âncoras e botões

Visualmente os elementos <a> e <button type="button"> podem ser exatamente iguais, pois para isso basta que sejam estilizados apropriadamente, porém semanticamente eles são diferentes e isso para usuários com necessidades especiais particularmente aqueles com restrições de exercer ações de apontar e clicar, tais como aqueles incapazes de usar o mouse, poderá ser desastroso.

Elemento âncora

O elemento <a> representa um hyperlink cujo destino é uma página ou uma seção dentro da própria página. O rótulo deste elemento é o seu conteúdo que poderá ser qualquer um, desde uma imagem até um elemento adicional não interativo tais como elementos <div>, <p>, etc.

Uma vez que o elemento <a> relaciona-se a um link, para ele estão disponíveis as pseudo-classes CSS :link e :visited. E também as pseudo-classes :hover, :active e :focus relacionadas à interação do usuário.

Ao se navegar com uso do teclado os elementos âncora podem ser "clicados" quando se pressiona a tecla Enter. Os leitores de tela, quando um link é clicado, esperam que o usuário navegue para uma página externa ou para uma seção na mesma página.

Elemento botão

O elemento <button>, ou mais especificamente o elemento<button type="button"> não faz nada. Ao contrário de outros tipos de botão, tal como o <button type="reset"> que reseta (limpa) os campos de um formulário o <button type="button"> não tem um comportamento padrão e em consequência não desencadeia nenhum tipo de ação.

Pelo fato de que nenhum link está associado ao botão, somente as pseudo-classes CSS de ação do usuário (:hover, :active e :focus) estão disponíveis para este elemento. Assim botões podem ser estilizados facilmente como se elementos âncora fossem.

Na navegação com uso de teclado, botões podem ser "clicados" ao se pressionar a tecla Enter ou a tecla para inserção de espaço, sendo esta a mais comumente usada. Para leitores de tela a expectativa de ação quando o usuário clica um botão na página é de que seja acionado um script JavaScript de interação. Alguns leitores de tela anunciam o elemento como sendo um botão dizem ao usuário: "click".

Quais são os problemas?

Problema com botões

Em primeiro lugar quero repetir que se a sua marcação se parece com a mostrada a seguir ...

<a href="#" role="button" onclick="openMenuModal();">Navegação</a>  
Não faça isso

... você deveria usar um botão.

Contudo, em alguns casos, usar um botão isoladamente não é necessariamente a solução mais adequada.

O elemento <button type="button">, como já dissemos, não faz nada. É preciso usar JavaScript para adicionar funcionalidade ao botão. Por outro lado, se JavaScript estiver desabilitado no dispositivo do usuário, ele não terá como interagir com o botão que no nosso exemplo seria acessar o menu de navegação.

Embora tenhamos feito tudo certo para leitores de tela usando uma marcação semântica bloqueamos acesso aos usuários com JavaScript desabilitado.

Problema com âncoras

A solução para estes casos é usar o elemento âncora modificando-o de duas formas distintas conforme descritas a seguir.

  1. Disponibilizar uma alternativa expressiva no atributo href;
  2. Alterar a semântica do elemento âncora de modo a que ele se pareça e se comporte como um botão para leitores de tela.

Observe a marcação a seguir que esclarece o que foi dito.

<a href="#navigation-alternative" role="button" onclick="openMenu();">Navegação</a>

Assim, se o dispositivo do usuário está com JavaScript desabilitado o acesso ao conteúdo da navegão será possível. Adicionalmente, uma vez que o botão é "clicável" com uso da tecla de espaço é possível atrelar o evento click à âncora-botão como mostrado a seguir.

JAVASCRIPT
var bindSpaceKey = function(anchor) {  
    anchor.addEventListener('keyup', function(event) {
        if (event.keyCode == 32) { anchor.click(); }
    })
}
var anchorButtons = document.querySelectorAll('a[role="button"]');  
for ( var i = 0; i < anchorButtons.length; i++ ) {  
    bindSpaceKey( anchorButtons[i] );
}

Na minha opinião, embora esta solução seja melhor do que usar um botão, ela não é perfeita. Primeiro: se JavaScript não está habilitado o elemento âncora deverá ser semanticamente um link e não um botão. Mas, uma vez que definimos role="button" para a âncora, ela será sempre um botão para os leitores de tela, mesmo nos casos em que não o é.

Segundo: ainda que tenhamos definido role="button" para a âncora eu não estou inteiramente convencida de que isso faça da âncora um elemento exatamente igual a um botão. Ainda que os leitores de tela e demais agentes de usuário devam tratá-la como um botão, não está garantido que o farão (link abre em nova janela).

Qual é a solução

Estamos diante de um dilema.

O elemento <button> é semanticamente correto, mas não atende o quesito melhoria progressiva (progressive enhancement) e, ao contrário, o elemento <a> atende o quesito melhoria progressiva, mas é semanticamente impróprio.

Assim, a única solução que eu vejo é alterar o elemento usado baseado nas circunstâncias. Em nome da consistência e da semântica, vamos usar JavaScript para vincular a tecla de espaço ao evento click em <a role="button">. Quando Javascript estiver desabilitado o elemento permanece como um link regular e semanticamente correto. Mas, quando Javascript estiver habilitado o elemento âncora é trocado por um elemento botão.

Na marcação HTML usamos um link regular sem modificações. Observe a seguir a solução proposta.

HTML
<a href="#navigation-alternative" class="anchorButton">Navegação</a>  

Se JavaScript estiver habilitado usamos esta linguagem para trocar todas as âncoras por botões.

JAVASCRIPT
var changeAnchorToButton = function(anchor) {

    // Criar elemento botão com os mesmos conteúdos e atributos das âncoras
    var button = document.createElement('button');
    button.setAttribute('type', 'button');
    button.innerHTML = anchor.innerHTML;
    button.id = anchor.id;
    for ( var i = 0; i < anchor.classList.length; i++ ) {
        button.classList.add(anchor.classList[i]);
    }

    // Substitui âncoras por botões
    var anchorParent = anchor.parentNode;
    anchorParent.replaceChild(button, anchor);
}
var anchorButtons = document.getElementsByClassName('anchorButton');  
for ( var i = 0; i < anchorButtons.length; i++ ) {  
    changeAnchorToButton( anchorButtons[i] );
}

Podemos estilizar o seletor .anchorButton de modo que tanto <a> quanto <button> sejam visualmente iguais como mostrado a seguir.

CSS
.anchorButton {
  /* Resets */
  font-family: sans-serif;
  font-size: 16px;
  color: #000;
  border: 1px solid #000;
  border-radius: 0;
  background-color: transparent;

  margin: 0;
  padding: 0;
  height: auto;
  width: auto;
  cursor: pointer;

  /* Mais estilos... */
}
a.anchorButton {  
  /* Estilos para elementos <a> */
  text-decoration: none;
}
button.anchorButton {  
  /* Estilos para elementos <button> */
  -webkit-appearance: none;
  -moz-appearance: none;
}

Embora esta solução seja radical, foi a única que eu consegui desenvolver atendendo os critérios de consistência visual, semântica e acessibilidade. E, a estilização conveniente do seletor .anchorButton não afetará a consistência visual da página como um todo.