O perigo da propriedade CSS list-style-type: none para listas acessíveis na web

A propriedade CSS list-style-type: none remove os bullet points das listas, mas também remove o significado semântico delas. Veja como corrigir isso.

  • list style type none
  • acessibilidade
  • css
  • lista
  • html
  • semântica
Front-end

Shortcut

Se usar list-style-type: none para remover um marcador de lista, faça também uma dessas opções:

  • Adicione o atributo role="list" no elemento de lista

  • Adicione li::before { content: "\200B" } no item de lista

Isso serve para a lista não perder o significado semântico no leitor de tela VoiceOver no Safari.

A exceção a isso são listas dentro da tag <nav>.

Você já usou list-style-type: none para remover um marcador de lista?

Legal, mas e a acessibilidade, como ficou?

Hoje vou te contar:

  • O impacto disso na árvore de acessibilidade

  • Duas formas simples de corrigir

  • A importância do CSS para a acessibilidade

  • A discussão em torno da decisão da Apple

Vem!

Por que usar list style type none?

Para remover o marcador de listas não textuais.

Não são apenas os textos que usam as tags de lista (como você vê muito ao longo dos meus posts), mas também para outras coisas. Menus de navegação e até grids de produtos em um ecommerce são feitos assim.

o menu principal no site da nike feito com tags de lista
o grid de produtos no site da farmrio feito com tags de lista

E para remover o marcador de lista, se usa o list-style-type: none.

Caso contrário, esses menus e grids ficariam assim:

o menu principal no site da nike com marcadores de lista
o grid de produtos no site da farmrio com marcadores de lista

Mas por que é preciso ter cuidado com essa propriedade?

Qual o problema de usar list style type none?

Acessibilidade com o leitor de tela VoiceOver no Safari.

Quando um leitor de tela encontra uma tag <ul> ou <ol>, ele envia mensagens com as seguintes informações:

  • Encontrou uma lista

  • A quantidade de <li>s dessa lista

  • Cada um dos itens de forma individual

  • E o fim dela

Uma pessoa usuária que usa leitor de tela pode pular uma lista inteira caso deseje.

O problema é que a propriedade list-style-type: none remove a semântica de lista dessas tags. Assim, o VoiceOver não sabe mais que aquilo é uma lista (é como se virasse uma <div>), e obriga a pessoa a ouvir todos os itens.

Ao remover o marcador, essa tag perde a aparência de lista. E a Apple entende que ela não deve ser considerada uma lista, caso não se pareça com uma. Não importa se as pessoas usuárias enxergam ou usam leitores de tela.

Já falei que isso acontece apenas no VoiceOver com o Safari. Mesmo assim, a Apple não vê isso como um bug, e sim uma feature (costumo dizer essa frase no trabalho).

Segundo ela, como se usa lista com frequência, os leitores de tela avisavam início da lista e fim da lista muitas vezes. Um incômodo. Ao remover a semântica de listas daquelas com list-style-type: none, esses anúncios frequentes pararam.

Você pode acompanhar a explicação completa nessa thread de James Craig .

Em função desse problema, você deve parar de usar essa propriedade? Claro que não!

Como resolver a acessibilidade do list style type none?

Existem duas formas de fazer isso: via HTML ou via CSS.

Via HTML: use o atributo role="list"

Se você fizer uma pesquisa, escontrará essa solução na maioria dos lugares:

<ul role="list">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Não é bug</li>
</ul>
ul {
  list-style-type: none;
}

Caso você não conheça, o atributo role="list" define a semântica de lista para qualquer elemento HTML .

Não é preciso usar role="listitem" para devolver o significado semântico das <li>s.

James Craig comenta sobre o role="list" nesse tweet .


Apesar dessa solução ser o suficiente, acredito que a próxima faz mais sentido. Eu explico o porquê.

Via CSS: adicione um pseudo-elemento antes de cada <li>

O pseudo-elemento não pode ser vazio ou um espaço em branco:

li::before {
  /* errado */
  content: "";
  content: " ";
  
  /* certo */
  content: "g";
  content: "+";
}
uma lista em html que utiliza o sinal de "+" como um marcador para devolver a semântica de lista
Utilizar "+" como marcador para devolver a semântica da lista

E por que eu considero isso melhor, se preciso inserir algo indesejado nas listas? Porque existe um truque muito simples.

Na propriedade content, use o símbolo HTML zero width space. Esse símbolo é algo válido, porém invisível. Seu código é \200B:

ul {
  /* remove o marcador e a semântica */
  list-style-type: none;
}

li::before {
  /* devolve a semântica apenas */
  content: "\200B";
}
uma lista em html que utiliza o símbolo html zero width space como um marcador para devolver a semântica de lista
Utilizar o símbolo HTML zero width space como marcador para devolver a semântica da lista

Assim, a lista recebe a semântica de volta.

Não se esqueça de que o pseudo-elemento deve vir antes (::before) e não depois (::after) da <li>.

Motivos para eu preferir a solução via CSS

Acredito que essa seja a melhor opção por alguns motivos.

O primeiro é que ela evita a repetição da semântica. Segundo a W3C, é desnecessário e não recomendado que um elemento HTML use um atributo para declarar a mesma semântica que ele já possui de forma nativa . Ou seja, a tag <ul> não precisa de role="list", assim como a tag <button> não deve precisar de role="button".

Logo, os verificadores automáticos irão acusar semântica duplicada nesses elementos.

O segundo motivo é que a solução via HTML gera mais código, quanto mais listas, mais código. Com CSS, infinitas listas exigem apenas 1 linha para corrigir a semântica.

O terceiro motivo é que basta eu fazer isso uma vez. No HTML eu preciso lembrar de escrever role="list" para cada lista.

O quarto motivo é a manutenção de código. Ao ver um bloco de CSS com list-style-type: none, não é preciso ir atrás de todos as listas do HTML para saber se possuem role=list. Basta ver se o mesmo bloco de CSS insere o pseudo-elemento.

O quinto motivo é que em certas situações, você front-end, não terá acesso ao HTML de uma página (estranho, eu sei). Isso é muito comum com a página de checkout de alguns ecommerces. Então você só terá acesso ao CSS para resolver o problema.

O sexto e último motivo (meio bobinho, mas vamos lá) é resolver o problema com CSS, já que ele mesmo o causou.

Independente de você adotar a solução via HTML ou CSS, existe uma exceção. Nesse caso, você não precisa fazer nada.

Exceção ao list style type none

A tag <nav>.

Tudo que você leu até aqui já é uma questão bem específica. Ou seja, acontece apenas com o leitor de tela VoiceOver no navegador Safari.

Agora você verá a exceção da exceção. A semântica não é removida, caso a lista seja um elemento filho de uma tag <nav>.

<nav>
  <!--
    Esta <ul> mantém seu significado semântivo
    caso tenha a propriedade list-style-type: none;
  -->
  <ul>
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
  </ul>
</nav>

No exemplo acima, caso não houvesse uma tag <nav> ao redor da <ul>, sim, a semântica seria removida.

Acredito que apresentei soluções bem completas, porém...

Nem todo mundo gosta dessas soluções

Como disse antes, a Apple não considera esse caso do VoiceOver no Safari um bug. Ela fez isso para que as pessoas usuárias de leitores de tela não ouvissem infinitos anúncios de listas.

Eu concordo com a Apple (você ouvirá eu falar pouco isso), um elemento deve se parecer com o que ele é. Se algo não se parece com uma lista, não deve ser uma lista, independente da pessoa usuária usar ou não leitor de tela.

O problema é que para a Apple os marcadores são a única maneira de fazer algo se parecer como uma lista. Nisso eu discordo.

Como ela usa uma propriedade CSS para remover a semântica de lista, é coerente que ela devolva a semântica usando CSS. Nesse caso, o content: "\200B". Mais um motivo para eu preferir essa opção.

Independente da minha opinião, ou de toda discussão que isso gerou, de uma coisa eu tenho certeza. E é dessa coisa que você precisa se lembrar ao terminar de ler esse texto: o HTML não é o único responsável pela árvore de acessibilidade, pois o CSS também é.

O exemplo desse artigo não é o único, ou seja, remover o significado semântico das listas com o list-style-type: none.

Isso porque aplicar display: none ou visibility: hidden, além de remover o elemento da tela, remove também da árvore de acessibilidade. Segundo James Craig, são dezenas, se não centenas de propriedades CSS que afetam a acessibilidade .

Callback

A propriedade list-style-type: none remove o marcador de lista de tags <ul> e <ol>. Mas remove também a semântica de lista no leitor de tela VoiceOver no Safari.

Para resolver isso, coloque role="list" na tag de lista, ou insira um pseudo-elemento em cada <li>. Recomendo usar o li::before { content: "\200B" }, pois é um espaço com zero de largura.

Isso só acontece com o VoiceOver, no Safari e apenas se a tag de lista (<ul> ou <ol>) não for filha de uma tag <nav>.

Diferente do que algumas pessoas pensam, O CSS também é responsável pela acessibilidade, e não só o HTML.

Espero que goste de todo esse material que reuni. Tanto técnico e prático, quanto da discussão atrás da solução para a acessibilidade das listas.

Deixe seu comentário com o que você achou de tudo isso. Realizar a pesquisa para escrever esse artigo foi muito satisfatório 😄

Se você gostou desse assunto, signigica que considera acessibilidade um assunto muito importante. Então vou deixar mais 3 posts com esse mesmo tema, mas de outras perspecticas.

Nesse post falei sobre o uso do focus em HTML, CSS e Javascript. Aqui, usei alguns capítulos para falar da acessibilidade em links e botões. Por fim, outro post sobre acessibilidade, porém foquei nas imagens sem texto alternativo.

Continue estudando:

Veja outros posts sobre Front-end