Marcação
Tags de Seções Comuns
Antigamente era comum utilizar apenas a tag div
para separar
o conteúdo, e atribuir a sua função através de IDs e
classes. Entretanto, o HTML5 implementou tags novas que
pretendem diminuir a utilização das divs, tornando-as
quase obsoletas.
Abaixo, segue um exemplo do formato convencional em que os desenvolvedores organizam os elementos de uma página, através de tags div
, IDs e classes:
<div id="header">
<h1>Título</h1>
<div id="nav">
...
</div>
</div>
<div id="main">
<div class="section-article">
<div class="article-header">
...
</div>
<div class="article-content">
...
</div>
<div class="article-footer">
...
</div>
</div>
<div class="section-article">
<div class="article-header">
...
</div>
<div class="article-content">
...
</div>
<div class="article-footer">
...
</div>
</div>
</div>
<div id="sidebar">
...
</div>
<div id="footer">
...
</div>
No próximo exemplo, as diversas tags div
são substítuidas por tags que indicam o tipo de elemento, sem precisar de IDs e classes para diferenciá-los.
<header>
<h1>Título</h1>
<nav>
...
</nav>
</header>
<main>
<section>
<header>
...
</header>
<article>
...
</article>
<footer>
...
</footer>
</section>
<section>
<header>
...
</header>
<article>
...
</article>
<footer>
...
</footer>
</section>
</main>
<aside>
...
</aside>
<footer>
...
</footer>
Dessa forma, a organização do código é mais semântica, sendo que os recursos de Tecnologia Assistiva podem fazer uso dessas tags para melhorar a experiência de usuários com necessidades específicas. As principais marcações, para delimitação mais semântica de conteúdo, são:
Elemento | Descrição |
---|---|
header |
Define o cabeçalho de uma página ou seção. Muitas vezes contém um logotipo, o título do site e uma menu de navegação do conteúdo. |
section |
Define uma seção do Documento (permite também a utilização de mais de um cabeçalho de nível 1 numa página, um por section ). |
nav |
Define uma seção que contém apenas links e botões de navegação. |
article |
Define um conteúdo que pode existir de forma independente do resto da página. Esta Tag poderia ser uma postagem em um fórum, um artigo de revista ou jornal, uma entrada de log da Web, um comentário enviado pelo usuário, ou qualquer outro item independente do conteúdo. |
aside |
Define um elemento reservado do resto do conteúdo da página. Se for removido, o conteúdo restante ainda faz sentido. Geralmente são barras laterais (sidebars). |
footer |
Define o rodapé de uma página ou seção. Muitas vezes contém um aviso de copyright, links para informações relevantes, comentários de uma seção ou endereços para dar feedback |
main |
Define o conteúdo principal no documento, de maior importância. Existe apenas um elemento deste tipo em uma página. |
É importante notar que a utilização desse recurso sozinho não melhora significativamente a experiência com tecnologias assistivas, fazendo necessário a utilização de outros recursos que serão abordados ao longo deste documento, como ARIA.
WAI-ARIA
O que é?
ARIA é uma especificação técnica, publicada pela W3C, que discursa sobre como melhorar a acessibilidade páginas web, em particular como providenciar informação semântica de conteúdos dinâmicos e componentes de interface de usuário.
Essa especificação providencia uma série de propriedades, estados e "papéis" (roles) desenhados para definir informações consisas sobre elementos de interface de usuário que podem aperfeiçoar a acessibilidade e interoperabilidade do conteúdo web, permitindo que o desenvolvedor transmita corretamente o comportamento e a informação estruturada para tecnologias assistivas que agem diretamente na marcação do documento.
Lembre-se dos padrões web!
ARIA é uma específicação muito rica na criação de aplicações web acessíveis, mas lembre-se sempre de organizar o código de forma semântica e seguindos os padrões web. Dessa forma, não é preciso utilizar muitos recursos como ARIA, pois a página será naturalmente mais acessível.
Nota sobre os exemplos
Muitas formas de implementação e atribuição de recursos do WAI-ARIA não são padronizadas e, portanto, são subjetivas e costumam causar divergência de opiniões. Neste documento, é abordado diferentes pontos de vista sobre essa implementação. Qualquer sugestão de mudança será discutida e bem aceita, através da Repositório do código fonte do Web para Todos no GitHub.
Atributos Role
Estes atributos permitem atribuir a um elemento um “papel”, uma função, dentro de uma página. Existem cerca de 73 “roles”, mas eles não podem ser atribuídos de qualquer forma. Ao criar um slidebar, por exemplo, utilizando um gráfico, o leitor de tela poderá ler algo como “Gráfico, botão”, ao invés de algo mais significativo como “Slider, valor de 42 por cento”. Utilizando os roles é possível atribuir esse comportamento à um elemento e adicionar significado a ele.
Estados e Propriedades
Atributos de Widget
Veja a seção Comportamento (DOM) para ver os exemplos de atributos para widgets de ARIA.
Atributos de regiões dinâmicas (live)
aria-live
aria-atomic
aria-busy
aria-relevant
Atributos de "Arrastar e Soltar"
Veja a seção Comportamento (DOM) para ver os exemplos de atributos para widgets do tipo arrastar e soltar.
Atributos de Relacionamentos
Estados e Propriedades Globais
Considerações sobre Redundância com ARIA
Antes de começar a implementar todos os atributos ARIA possíveis, algumas considerações devem ser feitas, pois existem recomendações e regras para seu uso. Se você puder utilizar um elemento HTML ou atributo com a semântica e comportamento que você necessita já nativos, ao invés de re-atribuir propósito a um elemento e adicionar um role ARIA, estado ou propriedade, utilize apenas o elemento nativo. Existem três exceções nessa regra, segundo a W3C:
- Se o recurso está disponível no HTML mas não é implementado, ou é implementado mas não há suporte à acessibilidade.
- Se as restrições design visual excluem o uso de um elemento nativo em particular, porque o elemento não pode ser estilizado como necessário.
- Se o recurso não está disponível no HTML.
Também não modifique semânticas nativas, como adicionar uma tag p
com um role button
!
Para mais informações sobre a utilização de ARIA, visite o documento online da W3C Notes on Using ARIA in HTML (em inglês). Nova Página
Multimídia
Imagens
Por razões bastante óbvias, imagens não são um recurso completamente acessível. Pessoas com problemas de visão e até mesmo cegueira, por exemplo, tem dificuldades na interpretação de elementos que sejam muito visuais, e a presença de imagens como componentes essenciais no conteúdo de uma página podem dificultar o entendimento da mesma. Dessa forma, é necessário apresentar alternativas para que a informação contida na imagem possa ser compreendida.
Texto Alternativo
Através do atributo alt
, o desenvolvedor
pode criar um texto alternativo à imagem, cujo objetivo
seja descrever os elementos da imagem.
<img src="exemplo.png" alt="pessoa cega utilizando um computador de mesa e fones de ouvido">
Atenção especial para imagens decorativas!
Uma prática muito comum no desenvolvimento web é a utilização de imagens como recurso decorativo. Essas imagens não são essenciais para a compreensão da página, portanto elas não necessitam de texto alternativo.
Mas é importante não confundir um usuário que utiliza leitor de tela, por exemplo, que pode entender que essa imagem é essencial para a compreensão da página. Dessa forma, uma prática recomendada é a inserção da imagem decorativa através de CSS. Assim, agentes de usuário (como o leitor de tela) não interpretam o elemento como imagem.
Conteúdo Independente de Fluxo
As tags Figure e Figcaption
A tag figure
representa conteúdos independentes,
frequentemente com uma legenda (com a tag figcaption
), e referido como uma única
unidade. Esse elemento é relacionado com o fluxo principal
da página, mas a posição dele é independente deste fluxo.
Geralmente representa imagens, ilustrações, diagramas,
códigos ou outros esquemas, referenciados no conteúdo principal.
As tags que compõe essa funcionalidade são:
Exemplos
Neste exemplo, há uma imagem, e para a mesma há uma legenda:
<figure>
<img src=”imagem.png” alt=”bola de basquete num fundo branco”>
<figcaption> Figura 1. Bola de basquete</figcaption>
</figure>
Neste outro, há um elemento criado com a tag canvas, e sua
posição é independente do fluxo da página, e portanto melhor
utilizado com a tag figure
:
<figure>
<canvas id="example" role="img"></canvas>
<figcaption>Figura 2. Exemplo de uso da tag canvas</figcaption>
</figure>
Note aqui a utilização de um role
de imagem no elemento canvas
Também é possível utilizar códigos, e outros tipos de elementos:
<figure>
<figcaption>Exemplo de código PHP</figcaption>
<pre>
<code class="php">
<?php
echo ‘Olá Mundo!’;
?>
</code>
</pre>
</figure>
Áudio e Vídeo
Áudio
Uma das novidades que a quinta versão do HTML trouxe aos desenvolvedores foi a possibilidade de criar conteúdo multimídia de forma nativa. A vantagem aqui é que plugins pesados e ultrapassados, como Flash, se tornaram obsoletos. Os mesmos também continham diversos problemas relacionados à acessibilidade e performance.
A tag audio
foi criada para aprimorar a incorporação
de áudio em páginas web. Com ela, é possível adicionar múltiplas fontes,
arquivos de legenda, etc. Veja os exemplos à seguir:
<audio src="audio-example.ogg">
Seu navegador não tem suporte para reprodução nativa de áudio.
</audio>
O exemplo acima é a forma mais básica de reproduçao de áudio nativa no HTML5. Mas não é completamente acessível: navegadores que não tem suporte ao formato OGG de áudio não serão capazes de reproduzir.
<audio controls="controls">
Seu navegador não tem suporte para reprodução nativa de áudio.
<source src="audio-example.wav" type="audio/wav">
<source src="audio-example.ogg" type="audio/ogg">
<source src="audio-example.mp3" type="audio/mpeg">
</audio>
Este último exemplo, diferente do primeiro, possui suporte à diferentes formatos de áudio e, dessa forma, é mais capaz de ser executado sem maiores problemas na maioria dos navegadores modernos. Mas não devemos apenas nos preocuparmos com compatibilidade, ao falar de acessibilidade, mas também a disponibilização desse conteúdo em outros formatos de mídia, como texto. Vamos adiante:
<audio controls="controls">
Seu navegador não tem suporte para reprodução nativa de áudio.
<source src="audio-example.wav" type="audio/wav">
<source src="audio-example.ogg" type="audio/ogg">
<source src="audio-example.mp3" type="audio/mpeg">
<track kind="captions" src="audio-example.en.vtt" srclang="en" label="English">
<track kind="captions" src="audio-example.sv.vtt" srclang="pt" label="Português">
</audio>
Com a tag track
ainda podemos definir
tipos diferentes de conteúdo textual para o áudio. Veja
na seção à seguir mais opções para este elemento: Conteúdo Textual Alternativo (tag track
)
Conteúdo Textual Alternativo (tag track
)
A tag track
é um elemento que adiciona conteúdo textual alternativo
à áudios e vídeos. O atributo kind
desta tag define o tipo de conteúdo (ou seja,
para que serve este arquivo textual dentro do escopo da mídia) e
admite 5 tipos, sendo estes:
-
subtitles
: conteúdo do tipo legendas. Providencia traduções de conteúdo que não pode ser interpretado por quem vê ou escuta a mídia, como tradução em português de uma mídia em inglês. Também pode conter informação adicional, como por exemplo textos que aparecem em um vídeo. -
captions
: outro tipo de conteúdo de legenda. O diferencial é que este tipo pretende transcrever o áudio, mesmo que este não seja diálogo, e possivelmente também uma tradução. Inclui informação não verbal de importância, como telefone tocando ou efeitos sonoros. Adequado à usuários que possuem deficiências auditivas ou para reproduções sem áudio de um vídeo. -
descriptions
: descrição textual da mídia. Adequado à usuários com problemas de visão, como cegueira, ou em situações em que o vídeo não pode ser exibido. -
chapters
: títulos de capítulos, para serem usados quando o usuário está navegando pela mídia. -
metadata
: faixas utilizadas por scripts. Não é visível pelo usuário.
Além do atributo kind
, ainda há os atributos:
src
, apontando para o arquivo; label
,
apresentando um rótulo para o conteúdo textual; e srclang
,
indicando a língua em que está o conteúdo. Veja o exemplo à seguir, que
mostra o uso de track
em um vídeo:
<video controls poster="poster.gif">
<source src="video.mp4" type="video/mp4">
<source src="video.ogv" type="video/ogv">
<track kind="captions" src="transcricoes.vtt" srclang="pt">
<track kind="descriptions" src="descricoes.vtt" srclang="pt">
<track kind="chapters" src="capitulos.vtt" srclang="pt">
<track kind="subtitles" src="legendas_es.vtt" srclang="es">
<track kind="subtitles" src="legendas_en.vtt" srclang="en">
<track kind="subtitles" src="legendas_pt.vtt" srclang="pt">
<track kind="metadata" src="keyStage1.vtt" srclang="pt" label="Key Stage 1">
<track kind="metadata" src="keyStage2.vtt" srclang="pt" label="Key Stage 2">
<track kind="metadata" src="keyStage3.vtt" srclang="pt" label="Key Stage 3">
</video>
Vídeo
Assim como acontecia com mídias de áudio, vídeos na web também dependiam de plugins como Flash e apresentavam problemas de acessibilidade, e o recurso nativo aprimorou a inserção destas mídias na web. Veja os exemplos:
<video src="filme.ogg" poster="posterimage.jpg">
Seu navegador não possui suporte à videos.
</video>
Também podemos incrementar este vídeo com legendas, assim como no próximo exemplo:
<video src="filme.ogg" poster="posterimage.jpg">
Seu navegador não possui suporte à videos.
<track kind="subtitles" src="filme.pt.vtt" srclang="pt" label="Português">
<track kind="subtitles" src="filme.en.vtt" srclang="en" label="English">
<track kind="subtitles" src="filme.sv.vtt" srclang="sv" label="Svenska">
</video>
Assim como a tag áudio, podemos incrementar ainda mais o
vídeo com múltiplas fontes do mesmo arquivo em formatos
diferentes, da mesma forma. Também podemos especificar
diferentes tipos de conteúdo textual, como transcrições e
afins. Veja a seção à seguir para mais informações: Conteúdo Textual Alternativo (tag track
)
Formulário
Validação
Na área da segurança, a validação de dados é um processo de extrema importância. E mesmo que ela deva ser realizada no servidor, por questões de comodidade ela deve, também, ser implementada no lado do cliente. Entretanto, muitas vezes essa informação apresenta problemas para utilizadores de tecnologias assistivas, fazendo-se necessário o cuidado na implementação dela.
Campos Obrigatórios e Asterisco
Uma prática muito comum entre autores é indicar campos obrigatórios utilizando asterisco. Essa prática é interessante, pois demarca determinados campos como obrigatório sem utilizar muita informação textual.
O problema com o asterisco é que o mesmo não faz sentido se lido por um leitor de tela, por exemplo. Dessa forma, a informação de que este campo é obrigatório se perde, criando confusão para usuários de tecnologias assistivas.
Para validar campos de entrada obrigatórios, o
autor pode utilizar o atributo required
,
um recurso nativo do HTML5. Além deste atributo,
é possível utilizar JavaScript para a mesma função,
porém requer muito mais código. Veja o exemplo:
<label for="password">Senha*</label>
<input type="password" name="password" id="password" required>
Mas apenas validar o campo como obrigatório não é
suficiente: é necessário demonstrar que o campo é
obrigatório antes de validá-lo. Para tal, muitos
autores utilizam um asterisco, mas como dito anteriormente,
não é uma prática acessível. Para contornar isso, é possível
utilizar uma imagem de asterisco inserida através de CSS,
em um elemento que possua um texto "Campo obrigatório"
escondido. Para tal, o autor deve apenas utilizar um
elemento span
com uma classe de sua
escolha, e através de CSS deixar o texto "fora da
tela" e atribuindo como imagem de background a imagem
de um asterisco. Você pode utilizar imagens PNG, GIF, SVG,
ícones de alguma fonte específica (como Font Awesome,
Foundation Icons, etc) ou formatos de sua escolha. O exemplo a seguir refatora o anterior,
trocando o caractere do asterisco pela alternativa
acessível descrita:
HTML
<label for="password">Senha <span class="obrigatorio">campo obrigatório</span></label>
<input type="password" name="password" id="password" required>
CSS
.obrigatorio{
background-image: url('asterisco.png');
position: absolute;
left: -9999em;
}
Retorno de Erros e Regiões Dinâmicas
Mesmo que o recurso nativo do HTML5 para retornos seja acessível, como a informação de campos inválidos ou em branco, ainda é necessário que desenvolvedores criem seus próprios métodos para informar erros em um formulário. Um exemplo bastante comum é a validação de CPF, por exemplo.
Para tal, o desenvolvedor se obriga a utilizar bastante JavaScript, tanto puro quanto na forma de bibliotecas. Mas, muitas vezes, essas bibliotecas não utilizam recursos acessíveis, ou o autor não se preocupou em tornar essa informação mais compreensível para usuários que utilizam tecnologias assistivas.
Há duas formas de fazer isso de forma acessível, e ambas utilizam
ARIA e um elemento onde as mensagens irão ser inseridas, como
por exemplo um elemento div
: é necessário definir o atributo
role
com o valor alert
, ou o atributo aria-live
como
assertive
. Nas duas formas é necessário o atributo aria-atomic
como true
. Ambas irão anunciar imediatamente a mensagem
de erro do formulário assim que surgirem dentro do elemento div
.
A mensagem de erro é inserida neste elemento através de DOM. Veja os exemplos:
<div role="alert" aria-atomic="true" id="form-erro"></div>
<div aria-live="assertive" aria-atomic="true" id="form-erro"></div>
Há três formas de apresentar as mensagens de erros em formulários, e estas são:
- Em Janela de Alerta
- No topo do formulário
- Embutidas no campo em questão
Mensagens de Erro em Janelas de Alerta
Para utilizar uma janela de alerta para apresentar
erros de formulário ao usuário, no momento em que
o código de validação estiver rodando, a todo campo
que não tenha passado na validação é aberto um elemento
alert()
do JavaScript, informando o erro.
O problema deste tipo de validação é receber um alerta
para cada erro, fazendo com que assim a correção de um formulário
seja cansativa. Ainda assim, é uma forma interessante por apresentar
fácil acessibilidade.
Após isso, o desenvolvedor pode escolher a forma como o usuário pode corrigir o erro: pode apresentar outra janela de alerta do JavaScript, desta vez com um campo de entrada, para inserir novamente a informação da forma correta, ou mover o foco ao campo de entrada. Veja um exemplo à seguir:
HTML
<form class="form" action="index.html" method="post" name="formName" onsubmit="return validate()">
<label for="name">Nome:</label> <input type="text" name="name" id="name">
<input type="submit" name="Enviar" value="Enviar">
</form>
JS
function validate() {
var element = document.forms['formName']['name'];
if (element.value == "") {
alert('Nome não pode estar em branco!');
element.focus();
return false;
}
}
Exemplo de Validação de Formulário
Mensagens de Erro no Topo do Formulário
Uma boa forma de demonstrar os erros de um formulário é apresentá-los no topo do formulário, com uma âncora para cada item do formulário que estiver em desacordo. Isto é bom pois agrega todos os erros em uma única seção. O problema deste tipo de validação é que um usuário que utiliza apenas o teclado para navegação, junto com um leitor de tela, precisa ir até o topo do formulário para verificar os erros. Assim, ele pode se esquecer dos erros que existem e precisar voltar ao topo do formulário para relembrá-los. Veja o exemplo à seguir:
HTML
<span aria-live="assertive" role="alert" id="alert-form-area"></span>
<form class="form" action="index.html" name="formToValidate" method="post" onsubmit="return validate_form()">
<label for="formNameValidate">Nome</label> <input type="text" name="formNameValidate" id="formNameValidate">
<label for="formEmailValidate">Email</label> <input type="email" name="formEmailValidate" id="formEmailValidate">
<label for="ageValidate">Idade</label> <input type="number" name="age" id="ageValidate">
<input type="submit" name="submit" value="Enviar">
</form>
JS
function validate_form() {
if (document.getElementById('msg-alert')) {
document.getElementById('msg-alert').remove();
}
var ol = document.createElement('ol');
var container = document.getElementById('alert-form-area');
ol.id = 'msg-alert';
var labels = document.getElementsByTagName('label');
var bool = true;
for (var i = 0; i < document.forms['formToValidate'].length; i++) {
var element = document.forms['formToValidate'][i];
if (element.value == "") {
var link = document.createElement('a');
link.href = '#' + element.id;
for (var x = 0; x < labels.length; x++) {
if (labels[x].getAttribute('for') == element.id) {
var l = labels[x].innerHTML;
}
}
link.innerHTML = l;
var msg = document.createTextNode(' não pode estar em branco!');
var li = document.createElement('li');
li.appendChild(link);
li.appendChild(msg);
ol.appendChild(li);
bool = false;
}
}
if (!bool) {
container.appendChild(ol);
ol.focus();
}
return bool;
}
Exemplo de Validação de Formulário
Mensagem de Erro Embutida no Campo
Por último, é possível informar o erro no formulário diretamente no campo em questão. A maioria dos novos navegadores já implementam isso de forma básica em cima de campos de formulário, apresentando este tipo de mensagem para informar erros no campo. Ainda assim, formas alternativas de validação se fazem necessárias pela variedade de informações que são necessárias na aplicação que está sendo desenvolvida, como validação de CPF, por exemplo.
HTML
<form class="form" action="index.html" name="formToInlineValidate" method="post" onsubmit="return validate_form()">
<label aria-live="assertive" aria-atomic="true" for="formNameValidate">Nome</label> <input type="text" name="formNameValidate" id="formNameValidate">
<label aria-live="assertive" aria-atomic="true" for="formEmailValidate">Email</label> <input type="email" name="formEmailValidate" id="formEmailValidate">
<label aria-live="assertive" aria-atomic="true" for="ageValidate">Idade</label> <input type="number" name="ageValidate" id="ageValidate">
<input type="submit" name="submit" value="Enviar">
</form>
JS
function remove_olds(className) {
var warning_length = document.getElementsByClassName(className).length;
if (document.getElementsByClassName(className)) {
for (var i = 0; i < warning_length; i++) {
document.getElementsByClassName(className)[0].remove();
}
}
}
function span_msg(className, txt) {
var msg = document.createElement('span');
msg.className = className;
var txtNode = document.createTextNode(txt);
msg.appendChild(txtNode);
return msg;
}
function append_msg(msg, form_input) {
if (form_input.value == "") {
for (var i = 0; i < document.getElementsByTagName('label').length; i++) {
if (document.getElementsByTagName('label')[i].getAttribute('for') == form_input.id) {
document.getElementsByTagName('label')[i].appendChild(msg.cloneNode(true));
form_input.setAttribute('aria-invalid', 'true');
}
}
return true;
} else {
form_input.setAttribute('aria-invalid', 'false');
}
}
function validate_form() {
remove_olds('warning-msg');
var bool = true;
var forms = document.forms['formToInlineValidate'];
var msg = span_msg('warning-msg', ' (este campo está incorreto!)');
for (var i = 0; i < forms.length; i++) {
if (append_msg(msg, forms[i])) {
bool = false;
}
}
return bool;
}
Exemplo de Validação de Formulário
Widgets Alternativos para Formulários (ARIA)
Widget de botão:
Mesmo com o recurso já implementado, muitas vezes por questões de estilização, o desenvolvedor necessita criar um botão do zero. Para isso, é importante utilizar o role apropriado, além de disponibilizar todas as funções padrões de um botão, como executar sua ação com o clique da tecla Enter, com o clique do mouse, modificar o cursor e a aparência do botão em determinados eventos, entre outros. Veja o exemplo à seguir:
HTML
<span role="button" id="aria-button" class="span-btn" tabindex="0">OK!</span>
CSS
.span-btn {
background-color: blue;
color: white;
font-weight: bold;
padding: 1em;
cursor: pointer;
}
.span-btn:hover {
background-color: black;
}
.span-btn:active {
border: 3px solid red;
}
.span-btn:focus {
border: 1px dotted white;
}
JS
var btn = document.getElementById('aria-button');
function acaoBotao() {
if (e.type == 'click' || (e.type == 'keydown' && e.keyCode == '13')) {
// ação realizada pelo botão
alert('Botão Pressionado com sucesso!');
}
}
btn.addEventListener('click', acaoBotao);
btn.addEventListener('keypress', acaoBotao);
Exemplo de botão
Não utilize este role em elementos button
ou input
do tipo button, pois é redundante. Âncora para: Considerações Sobre Redundância com ARIA
Widget slider
Precisa de melhorias!
Neste exemplo há falta de JavaScript e CSS apropriados para a criação do widget.
<label for="day-handle">Dias da Semana</label>
<div class="day-slider">
<div id="day-handle" class="day-slider-handle" role="slider" aria-labelledby="day-handle"
aria-valuemin="1"
aria-valuemax="7"
aria-valuenow="2"
aria-valuetext="Segunda-feira">
</div>
</div>
var dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
var updateSlider = function (newValue) {
var handle = document.getElementById("day-handle");
handle.setAttribute("aria-valuenow", newValue.toString());
handle.setAttribute("aria-valuetext", dayNames[newValue]);
};
Widget de caixa de seleção e "interruptor" (switch):
HTML
<div class="example-checkbox">
<span role="checkbox" aria-checked="false" tabindex="0" id="chk1"></span>
<label for="chk1">Lembrar minhas preferências</label>
</div>
CSS
.example-checkbox span[aria-checked=false] {
width: 17px;
height: 17px;
background-color: grey;
display: inline-block;
cursor: pointer;
}
.example-checkbox span[aria-checked=true] {
width: 17px;
height: 17px;
background-color: green;
display: inline-block;
cursor: pointer;
}
JS
function trocarSelecao(e) {
if (e.type == 'click' || (e.type == 'keydown' & & e.keyCode == '13')) {
if (e.target.getAttribute('aria-checked') == 'true') {
e.target.setAttribute('aria-checked', 'false');
} else {
e.target.setAttribute('aria-checked', 'true');
}
}
}
var checkbox = document.getElementById('chk1');
checkbox.addEventListener('click', trocarSelecao);
checkbox.addEventListener('keypress', trocarSelecao);
Exemplo
Não utilize este role em elementos input
do tipo checkbox, pois é redundante. Âncora para: Considerações Sobre Redundância com ARIA
Também lembre-se que, se for extritamente necessário construir uma caixa de seleção dessa forma, configurações adicionais são necessárias para dar ao elemento a função de um link.
<span role="switch" aria-checked="false" tabindex="0" id="chk1"></span>
<label for="chk1">Lembrar minhas preferências</label>
Este role é muito parecido com o role de caixa de seleção, entretanto, não admite valor misto. Este tipo de valor deve ser tratado por agentes de usuário como igual a false
Para diferenciá-lo do exemplo da caixa de seleção, você pode alterar o CSS de forma que visualmente o elemento lembre um switch.
Widget de área de texto
Para criar um elemento de entrada de texto utilizando o role aria textbox
, é necessário adiocionar comportamentos específicos através de javascript e estilizar o elemento div
para que se comporte como uma entrada de texto. Lembre-se de capturar o conteúdo do elemento utilizando getElementByID('id').innerHTML
e, assim, poder realizar as funções que necessita.
HTML
<span id="aria-text-label">Insira o texto à seguir:</span>
<div role="textbox" contenteditable="true" aria-labelledby="aria-text-label" id="aria-text" aria-multiline="true"></div>
<button onclick="capturarConteudo();"></button>
CSS
#aria-text {
border: 1px solid black;
height: 100px;
}
JS
function capturarConteudo() {
var texto = document.getElementById('aria-text').innerHTML;
armazenarDado(texto);
}
function armazenarDado(texto) {
// desenvolva aqui seu código para trabalhar com os dados inseridos da forma como for necessária
}
Exemplo
O exemplo acima irá executar um alerta do navegador, exibindo o texto que foi escrito dentro do elemento recém criado.
Comportamento (DOM)
Widgets Acessíveis com ARIA
Existe uma infinidade de necessidades que desenvolvedores devem solucionar através da construção de widgets: componentes de interface de usuário que realizam determinadas funções de forma dinâmica. Entretanto, a maioria destes não possui forma de implementação nativa, ou a mesma não pode ser utilizada. Para tal, cabe ao desenvolvedor saber utilizar recursos específicos de acessibilidade, como o ARIA, para construir estes widgets. Veja os exemplos a seguir:
Widget de alerta e diálogo de alerta:
Ajude-nos!
Este exemplo precisa de aperfeiçoamento.
Os exemplos à seguir não necessitam de muita configuração por sere mais uma questão de marcação adequada, talvez com exceção do diálogo de alerta.
<p role="alert">Você deve ler e aceitar os termos de uso!</p>
<div role="alertdialog" aria-labelledby="titulo" aria-describedby="descricao">
<div role="document" tabindex="0">
<h2 id="titulo">Sua sessão de login está para expirar!</h2>
<p id="descricao">
Clique no botão abaixo para extender sua sessão
</p>
<button>OK</button>
</div>
</div>
Exemplos
Alerta
Você deve ler e aceitar os termos de uso!
Diálogo de Alerta
Widget de barra de progresso
Utilizando os roles e atributos de estado e propriedades adequados, uma barra de progresso que faria diferença apenas visual pode, agora, apresentar valores máximos, mínimos e ainda indicar o valor atual.
HTML
<div class="progress">
<div class="progress-bar" id="progress-bar"
role="progressbar"
aria-valuenow="20"
aria-valuemin="0"
aria-valuemax="100"
style="width:20%">
20 %
</div>
</div>
<button onclick="aumentar10('progress-bar');" >Aumentar 10%</button>
CSS
#progress {
height: 30px;
overflow: hidden;
background-color: #f5f5f5;
border: 1px solid black;
}
#progress-bar {
float: left;
font-size: 12px;
line-height: 20px;
color: #fff;
text-align: center;
background-color: blue;
transition: width .6s ease;
}
JS
function aumentar10(progressbarID) {
var barra = document.getElementById(progressbarID);
valuenow = barra.getAttribute('aria-valuenow');
if (valuenow != "100") {
valuenow = Number(valuenow) + 10;
barra.setAttribute('aria-valuenow', valuenow);
barra.style.width = String(valuenow) + '%';
barra.innerHTML = String(valuenow) + ' %';
}
return true;
}
Exemplo
Ao pressionar o botão, uma adição de 10% será dada na barra de progresso abaixo.
Widget de caixa de diálogo:
<div role="dialog" aria-labelledby="titulo" aria-describedby="descricao">
<h2 id="titulo">Mensagem enviada com sucesso!</h2>
<p id="descricao">
Sua mensagem foi enviada e será respondida brevemente.
</p>
<button>Fechar</button>
</div>
Widget de Arrastar e Soltar
HTML
<div id="alert-area" aria-live="assertive"></div>
<div id="maindrag">
<a href="#" class="indicator-anchor" style="display:none;">Início do componente de arrastar e soltar</a>
<div id="draggable" tabindex="0" draggable="true" ondragstart="event.dataTransfer.setData( 'text/plain',null)" aria-grabbed="false">
Lavar a louça
</div>
<div class="dropzone">
<span id="drop1">Para Fazer</span>
</div>
<div class="dropzone">
<span id="drop2">Fazendo</span>
</div>
<div class="dropzone">
<span id="drop3">Feito</span>
</div>
<a href="#" class="indicator-anchor" style="display:none;">Fim do componente de arrastar e soltar</a>
</div>
CSS
.dropzone {
color: white;
background-color: gray;
display: block;
width: 350px;
height: 200px;
padding: 1em;
margin: 0.5em;
}
#draggable {
cursor: grab;
color: white;
font-weight: bold;
background-color: black;
width: 300px;
padding: 0.5em;
}
#draggable:focus, #draggable:hover {
border: 3px solid red;
}
[aria-dropeffect=move] {
border: 5px solid black;
}
.drophover {
border: 5px solid red !important;
}
.dropzone span {
font-weight: bold;
}
.sronly, #alert-area, .indicator-anchor {
position: absolute;
left: -999em;
}
JS
var dragged;
var verify_dropELement = false;
var dnd_alert = document.getElementById('alert-area');
function dragStart(event) {
// TODO: limitar a navegação por tab apenas entre as dropzones quando o elemento arrastável é pegos
if (event.type == 'dragstart' || (event.type == 'keypress' & & event.keyCode == '13')) {
// Ativa âncoras de começo e fim do widget
var anchors = document.getElementsByClassName('indicator-anchor');
for (var i = 0; i < anchors.length; i++) {
anchors[i].removeAttribute('style');
}
// Anuncia que o elemento foi capturado
var msg = document.createTextNode("O elemento foi capturado");
dnd_alert.appendChild(msg);
// Remove o tabindex do elemento arrastável
dragged = event.target;
dragged.removeAttribute('tabindex');
var parent = dragged.parentNode;
// modifica o atributo aria-grabbed como true no elemento
event.target.setAttribute("aria-grabbed", true);
var dropzones = document.getElementsByClassName('dropzone');
for (var i = 0; i < dropzones.length; i++) {
dropzones[i].setAttribute("aria-dropeffect", "move");
dropzones[i].setAttribute('tabindex', '0');
}
}
}
function dragEnd(event) {
// modifica o atributo aria-grabbed para false
if (event.type == 'dragend' || (event.type == 'keypress' & & event.keyCode == '13' & & verify_dropELement)) {
if (verify_dropELement) {
var anchors = document.getElementsByClassName('indicator-anchor');
for (var i = 0; i < anchors.length; i++) {
anchors[i].setAttribute('style', 'display:none;')
}
var msg = document.createTextNode("O elemento foi largado");
dnd_alert.appendChild(msg);
event.target.setAttribute("aria-grabbed", false);
var dropzones = document.getElementsByClassName('dropzone');
for (var i = 0; i < dropzones.length; i++) {
dropzones[i].removeAttribute("aria-dropeffect");
dropzones[i].removeAttribute('tabindex');
dropzones[i].className = "dropzone"
}
}
verify_dropELement = false;
}
}
function dragOver(event) {
// previne o comportamento padrão para permitir que o elemento arrastado seja soltado dentro
event.preventDefault();
}
function dragEnter(event) {
var el = event.target;
if (el.className == "dropzone" || el.className == "dropzone drophover") {
// adiciona o atributo aria-dropeffect como move, indicando que o efeito realizado ao arrastar e soltar um elemento é movê-lo
el.className = "dropzone drophover";
}
}
function dragLeave(event) {
var el = event.target;
if (el.className == "dropzone" || el.className == "dropzone drophover") {
// remove o atributo quando o elemento arrastado não está mais sobre este alvo
el.className = "dropzone";
}
}
function dropElement(event) {
if (event.type == 'drop' || (event.type == 'keypress' & & event.keyCode == '13' & & !verify_dropELement)) {
// previne a ação padrão (abre como link para alguns elementos)
event.preventDefault();
var el = event.target;
// move o elemento arrastado para o alvo
dragged.setAttribute('tabindex', 0);
if (el.className == "dropzone" || el.className == "dropzone drophover") {
el.appendChild(dragged);
}
verify_dropELement = true;
}
}
var main = document.getElementById('maindrag');
var draggable = document.getElementById('draggable');
var dropzones = document.getElementsByClassName('dropzone');
draggable.addEventListener('keypress', dragStart, false);
main.addEventListener("dragstart", dragStart, false);
main.addEventListener("dragend", dragEnd, false);
main.addEventListener("dragover", dragOver, false);
main.addEventListener("dragenter", dragEnter, false);
main.addEventListener("dragleave", dragLeave, false);
main.addEventListener("drop", dropElement, false);
for (var i = 0; i < dropzones.length; i++) {
dropzones[i].addEventListener('focus', dragEnter, false);
dropzones[i].addEventListener('blur', dragLeave, false);
dropzones[i].addEventListener('keypress', dropElement, true);
dropzones[i].addEventListener('keypress', dragEnd, true);
}
Exemplo
Para utilizar o componente de arrastar e soltar deste exemplo, utilizando um teclado, as instruções são bastante simples. Confira a lista abaixo:
- Selecione o elemento "Lavar a Louça" utilizando a tecla Enter.
- Navegue pelas áreas em que o elemento pode ser largado utilizando a tecla tab.
- Selecione a área desejada utilizando a tecla Enter.
- Há âncoras para indicar o início e fim da seção em que existe áreas para largar o elemento.
Widget de link:
HTML
<span role="link" id="link" tabindex="0">Site do Centro Tecnólógico de Acessibilidade</span>
CSS
[role=link] {
color: #0064b4;
}
[role=link]:focus, [role=link]:hover {
color: #23527c;
text-decoration: underline;
cursor: pointer;
}
JS
var link = document.getElementById('link');
function linkToCTA(e) {
if (e.type == 'click' || (e.type == 'keydown' && e.keyCode == '13')) {
window.location.href = 'http://cta.ifrs.edu.br';
}
}
link.addEventListener('click', linkToCTA);
link.addEventListener('keypress', linkToCTA);
Exemplo
Site do Centro Tecnólógico de AcessibilidadeNão utilize este role em links nativos do HTML, pois é redundante. Âncora para: Considerações Sobre Redundância com ARIA
Também lembre-se que, se for extritamente necessário construir um link dessa forma, configurações adicionais são necessárias para dar ao elemento a função de um link.
Widget de log e marquee:
<div id="chat" role="log" aria-live="polite">
...
</div>
Utilizar role log implica que a região possui o valor polite
para o a propriedade aria-live.
<div class="algumacoisa" role="marquee" aria-live="polite">
...
</div>
Utilizar role marquee implica que a região possui o valor off
para o a propriedade aria-live.
Substitua o role log por marquee em casos em que a região live é atualizada com informação que não é relevante.
Exemplo do widgets de list, listbox e combobox
Os exemplos abaixo demonstram a utilização de listas e listas interativas, contendo opções ou funcionando de forma similar a elementos select
. No primeiro, temos um exemplo de uma lista simples e não interativa.
<div class="lista" role="list">
<div role="listitem">Ovos</div>
<div role="listitem">Farinha</div>
<div role="listitem">Leite</div>
<div role="listitem">Pão</div>
</div>
Não utilize este role em listas nativas do HTML, pois é redundante. Âncora para: Considerações Sobre Redundância com ARIA
Para criar listas interativas, há roles diferentes de list
, e um deles é o listbox
. No exemplo abaixo, há uma lista de itens selecionáveis.
<div role="listbox" tabindex="0" id="listbox1" aria-activedescendant="listbox1-1">
<div role="option" id="listbox1-1" class="selected">Verde</div>
<div role="option" id="listbox1-2">Laranja</div>
<div role="option" id="listbox1-3">Vermelho</div>
<div role="option" id="listbox1-4">Azul</div>
<div role="option" id="listbox1-5">Preto</div>
<div role="option" id="listbox1-6">Violeta</div>
</div>
Outro role para listas interativas é o combobox
, muito semelhante à textbox
e utilizado em widgets que sejam como um elemento select
, porém com uma entrada de usuário para busca ou adição de novo item. Note que ainda há uma lista utilizando listbox
abaixo do elemento input
com o role combobox
.
<input type="text"
aria-label="Tag"
role="combobox"
aria-expanded="true"
aria-autocomplete="list"
aria-owns="listbox"
aria-activedescendant="opcao_selecionada">
<ul role="listbox" id="listbox">
<li role="option">Zebra</li>
<li role="option" id="opcao_selecionada">Zoom</li>
</ul>
É imporante lembrar que nos casos dos roles listbox
e combobox
, precisa-se desenvolver todas operações através de JavaScript, como a seleção de opções e afins.
Widget de menu:
HTML
<ul role="menu" id="vertical-menu" tabindex="0">
<li tabindex="0" role="menuitem">Item 1</li>
<li tabindex="0" role="menuitem">Item 2</li>
<div role="group" id="radiogroup">
<li tabindex="0" class="radio-item" role="menuitemradio" aria-checked="false">Opção 1</li>
<li tabindex="0" class="radio-item" role="menuitemradio" aria-checked="false">Opção 2</li>
<li tabindex="0" class="radio-item" role="menuitemradio" aria-checked="true">Opção 3</li>
</div>
</ul>
<div role="menubar" id="horizontal-menu" tabindex="0">
<span tabindex="0" role="menuitem">Novo Documento</span>
<span tabindex="0" role="menuitem">Abrir Documento</span>
<div role="group">
<span tabindex="0" class="checkbox-item" role="menuitemcheckbox" aria-checked="true">Salvar Automaticamente</span>
<span tabindex="0" class="checkbox-item" role="menuitemcheckbox" aria-checked="false">Salvar na Nuvem</span>
</div>
</div>
CSS
li[role=menuitem], li[role=menuitemradio], li[role=menuitemcheckbox] {
list-style: none;
display: block
}
ul[role=menu] li, div[role=menubar] span {
background-color: black;
padding: 1em;
color: white;
font-weight: bold;
}
ul[role=menu] li:hover, div[role=menubar] span:hover, ul[role=menu] li:focus, div[role=menubar] span:focus {
background-color: lightgrey;
color: black;
}
span[role=menuitem], span[role=menuitemradio], span[role=menuitemcheckbox] {
list-style: none;
margin-bottom: 0.5em;
margin-right: 0;
margin-left: 0;
display: inline-block
}
[aria-checked="true"] {
background-color: grey !important;
}
JS
function mudarEstado(e){
if (e.type == 'click' || (e.type == 'keydown' && e.keyCode == '13')) {
var obj = e.target;
if (obj) {
var checked = obj.getAttribute('aria-checked')
if (checked == 'true') {
obj.setAttribute('aria-checked', 'false');
}
else {
obj.setAttribute('aria-checked', 'true');
}
}
}
}
var elementos = document.getElementsByClassName("checkbox-item");
for (var i = 0; i < elementos.length; i++) {
elementos[i].addEventListener('click', mudarEstado);
elementos[i].addEventListener('keydown', mudarEstado);
}
var group = document.getElementById('radiogroup');
function mudarEstadoRadio(e) {
if (e.type == 'click' || (e.type == 'keydown' && e.keyCode == '13')) {
var group = document.getElementById('radiogroup');
for (var i = 0; i < group.children.length; i++) {
var checked = group.children[i].getAttribute('aria-checked')
if (checked == 'true') {
group.children[i].setAttribute('aria-checked', 'false');
}
}
var obj = e.target;
if (obj) {
var checked = obj.getAttribute('aria-checked')
if (checked == 'true') {
obj.setAttribute('aria-checked', 'false');
}
else {
obj.setAttribute('aria-checked', 'true');
}
}
}
}
var elements = document.getElementsByClassName("radio-item");
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener('click', mudarEstadoRadio);
elements[i].addEventListener('keydown', mudarEstadoRadio);
}
Enquanto menu
representa um menu de forma genérica, menubar
necessariamente representa um menu horizontal.
Menus não precisam, necessariamente, serem atribuidos à listas (elemento ul
).
Widget de status:
<p role="status">Suas mudanças foram salvas automaticamente.</p>
gridcell
:radio
:scrollbar
:spinbutton
:tab
:tabpanel
:textbox
:timer
:tooltip
:treeitem
: