ARIA
Enriqueça a semântica de componentes complexos para torná-los acessíveis
O que é ARIA?
ARIA (Accessible Rich Internet Applications) é uma especificação W3C que permite melhorar a acessibilidade de aplicações web ricas e componentes dinâmicos.
🏷️ Papéis
Define o que um elemento é (botão, navegação, alerta...)
role="button"📊 Estados
Descrevem o estado atual de um elemento (marcado, expandido, desabilitado...)
aria-expanded="true"🔧 Propriedades
Fornecem informações adicionais (rótulo, describedby, controls...)
aria-label="Menu principal"⚠️ Regra de Ouro do ARIA
Não use ARIA se você pode usar HTML nativo
Formulários com atributos ARIA
Exemplo ruim
Formulário sem ARIA
❌ Formulário inacessível sem atributos ARIA apropriados:
<!-- Ruim: sem rótulo, sem descrição -->
<input type="text" placeholder="Seu e-mail">
<!-- Ruim: ícone não acessível -->
<button>
<span class="icon">🔍</span>
</button>
<!-- Ruim: erro não associado, sem autocompletar -->
<input type="password">
<div class="error">Senha muito curta</div>Exemplo bom
Formulário com ARIA
✅ Formulário acessível com atributos ARIA apropriados:
<!-- Bom: aria-label para campo sem rótulo visível -->
<input
type="text"
aria-label="Endereço de e-mail (obrigatório)"
aria-describedby="email-desc"
aria-required="true">
<span id="email-desc">Formato: nome@exemplo.com</span>
<!-- Bom: botão com aria-label -->
<button aria-label="Enviar formulário">
<span aria-hidden="true">🔍</span>
</button>
<!-- Bom: erro associado com aria-describedby -->
<input
type="password"
aria-label="Senha (mínimo de 8 caracteres)"
aria-invalid="true"
aria-describedby="pwd-error"
autocomplete="new-password">
<div id="pwd-error" role="alert">
Senha deve conter pelo menos 8 caracteres
</div>Atributos ARIA utilizados:
aria-label: Fornece um rótulo acessívelaria-describedby: Associa uma descrição detalhadaaria-required: Indica um campo obrigatórioaria-invalid: Sinaliza um campo inválidoaria-labelledby: Referencia um rótulo existenterole="alert": Anuncia erros imediatamentearia-hidden: Oculta elementos decorativosaria-live: Anuncia mudanças dinâmicas
Explicação: Atributos ARIA enriquecem formulários fornecendo rótulos, descrições e estados para tecnologias assistivas.
Accordion acessível
Exemplo ruim
Sem atributos ARIA
<!-- Ruim: Sem atributos ARIA, sem navegação por teclado -->
<div class="accordion-header" @click="toggle">
<span>Title</span>
<span>+</span>
</div>
<div v-if="open">
Content
</div>Exemplo bom
Com ARIA adequado
<!-- Bom: ARIA completo com navegação por teclado -->
<button
id="accordion-button-1"
aria-expanded="false"
aria-controls="panel-1"
@click="toggle"
@keydown.enter="toggle"
>
Title
</button>
<div
id="panel-1"
role="region"
aria-labelledby="accordion-button-1"
:hidden="!open"
>
Content
</div>Explicação: Um accordion deve usar atributos ARIA apropriados, suportar navegação por teclado e anunciar mudanças de estado.
Tabindex e navegação por teclado
O atributo tabindex controla se um elemento pode receber foco do teclado e em que ordem.
✅ tabindex="0"
Elemento é focalizável na ordem natural do DOM
tabindex="0"<!-- Bom: Elemento interativo focalizável -->
<div role="button" aria-label="Custom action" tabindex="0">
Custom button
</div>⚠️ tabindex="-1"
Focalizável por JavaScript, mas não por Tab
tabindex="-1"<!-- Bom: Para foco programático -->
<div role="alert" aria-live="assertive" tabindex="-1">
Error message
</div>❌ tabindex > 0
Altera a ordem de navegação (evite!)
tabindex="5"<!-- Ruim: Quebra a ordem natural de tabulação -->
<button tabindex="3">Third</button>
<button tabindex="1">First</button>
<button tabindex="2">Second</button>📋 Tabindex best practices
- Use
tabindex="0"para tornar elementos interativos personalizados focalizáveis - Use
tabindex="-1"para foco programático (modais, alertas) - NUNCA use valores positivos - respeite a ordem do DOM
- Elementos interativos nativos (
<button>,<a>,<input>) não precisam de tabindex
🎯 Interactive examples
Teste a navegação por teclado (Tab/Shift+Tab) com estes elementos:
❌ Elementos não focalizáveis
Parágrafo
💡 Testing tips
- Use Tab para navegar para frente
- Use Shift + Tab para navegar para trás
- Use Espaço ou Enter para ativar elementos
- Observe o indicador de foco (contorno azul)
- Observe quais elementos podem ou não receber foco
Menu suspenso acessível
Exemplo ruim
Menu sem ARIA
<!-- Ruim: Sem papel de menu, sem armadilha de foco -->
<div @click="open = !open">
Actions ▼
</div>
<div v-if="open">
<div @click="action">Edit</div>
<div @click="action">Delete</div>
</div>Exemplo bom
Menu com ARIA
<!-- Bom: Atributos ARIA e gerenciamento de foco -->
<button
aria-haspopup="true"
aria-expanded="false"
aria-controls="menu"
@keydown.escape="close"
>
Actions
</button>
<div
id="menu"
role="menu"
v-if="open"
>
<button role="menuitem">Edit</button>
<button role="menuitem">Delete</button>
</div>Explicação: A dropdown menu must support keyboard navigation, close with Escape, and maintain focus appropriately.
Galeria de imagens acessível
Exemplo ruim
Galeria de imagens acessível
<!-- ❌ Ruim: galeria não acessível por teclado -->
<div class="thumbnails">
<img v-for="image in images"
:src="image.thumb"
@click="selectImage(index)"
:class="{ active: selected === index }" />
</div>
<div class="main-image">
<img :src="images[selected].full" />
</div>Exemplo bom
Galeria de imagens acessível
Vista frontal do MacBook Pro de 16 polegadas com a tela fechada, mostrando o design elegante em alumínio.
<!-- ✅ Bom: galeria acessível com navegação por teclado -->
<div role="tablist" aria-label="Imagens do produto">
<button v-for="(image, index) in images"
:id="`tab-${index}`"
role="tab"
:aria-selected="selected === index"
:aria-controls="`panel-${index}`"
:tabindex="selected === index ? 0 : -1"
@click="selectImage(index)"
@keydown="handleKeydown($event, index)">
<img :src="image.thumb" :alt="`${image.alt} - Miniature`" />
</button>
</div>
<div :id="`panel-${selected}`"
role="tabpanel"
:aria-labelledby="`tab-${selected}`">
<img :src="images[selected].full" :alt="images[selected].alt" />
<p>{{ images[selected].description }}</p>
</div>
<!-- Bonnes pratiques :
- Pattern tablist/tab/tabpanel ARIA
- Navigation avec flèches, Home, End
- Focus management avec tabindex
- Descriptions détaillées pour chaque image -->Explicação: An image gallery must allow keyboard navigation and provide contextual descriptions for each image. The ARIA tablist/tab/tabpanel pattern is perfect for this use case.
Tabela ordenável acessível
Exemplo ruim
| Nome ↑ | Função | Status | |
|---|---|---|---|
| Alice Martin | alice@example.com | Administrador | Ativo |
| Bob Dupont | bob@example.com | Usuário | Inativo |
| Charlie Leroy | charlie@example.com | Moderador | Ativo |
| Diana Moreau | diana@example.com | Usuário | Suspenso |
Exemplo bom
| Alice Martin | alice@example.com | Administrador | Ativo |
|---|---|---|---|
| Bob Dupont | bob@example.com | Usuário | Inativo |
| Charlie Leroy | charlie@example.com | Moderador | Ativo |
| Diana Moreau | diana@example.com | Usuário | Suspenso |
Explicação: A sortable table must indicate the current sort state, allow keyboard navigation, and announce changes to screen readers.
Regiões aria-live dinâmicas
Exemplo ruim
Sem aria-live
Mudanças não são anunciadas
<!-- Mauvais : pas d'attribut aria-live -->
<div class="status-banner">
<p></p>
</div>Contador: 0
Problemas:
- Mudanças não são anunciadas
- Usuários de leitores de tela não sabem que o conteúdo mudou
- Sem indicação da importância da mensagem
Exemplo bom
Com aria-live apropriado
Mudanças são anunciadas de acordo com sua prioridade
<!-- Bon : avec aria-live="polite" pour informations -->
<div
class="status-banner"
aria-live="polite"
aria-atomic="true"
role="status"
>
<p>Information : Données mises à jour</p>
</div>
<!-- Bon : avec aria-live="assertive" pour alertes -->
<div
class="alert-banner"
aria-live="assertive"
aria-atomic="true"
role="alert"
>
<p>Attention : Action requise immédiatement</p>
</div>Zona educada (informação)
Zona assertiva (alertas)
Vantagens:
- Mensagens importantes interrompem com aria-live="assertive"
- Mensagens informativas aguardam com aria-live="polite"
- aria-atomic="true" anuncia todo o conteúdo da região
- Papéis apropriados (status, alert) fornecem mais contexto
Explicação: Aria-live regions allow announcing content changes to screen readers. 'polite' waits for the current announcement to finish, 'assertive' interrupts immediately.
Botões de ícone acessíveis
Exemplo ruim
Exemplo bom
Explicação: Buttons that contain only icons must have an aria-label or visually hidden text to be understood by screen readers.
Combobox / Pesquisa com sugestões
Exemplo ruim
Sem ARIA ou navegação por teclado
Exemplo bom
Com ARIA e navegação por teclado
Explicação: A search with suggestions (combobox) must be keyboard navigable, announce results and allow selection.
Notificações toast (Regiões dinâmicas)
Exemplo ruim
Sem aria-live
❌ Notificações não são anunciadas para leitores de tela
<!-- Ruim: não anunciado para leitores de tela -->
<div v-if="showToast" class="toast">
Votre demande a été enregistrée
</div>Exemplo bom
Com aria-live apropriado
✅ Notificações são devidamente anunciadas
<!-- Bom: notificação informativa (educado) -->
<div
v-if="showToast"
role="status"
aria-live="polite"
aria-atomic="true"
class="toast"
>
<p>Votre demande a été enregistrée</p>
<button @click="dismiss" aria-label="Fermer notification">✕</button>
</div>
<!-- Bom: alerta de erro (assertivo) -->
<div
v-if="showError"
role="alert"
aria-live="assertive"
aria-atomic="true"
class="toast error"
>
<p>Erreur : action impossible à exécuter</p>
<button @click="dismiss" aria-label="Fermer alerte">✕</button>
</div>Diferenças:
role="status"+aria-live="polite": Para notificações informativas, não interrompe a leitura atualrole="alert"+aria-live="assertive": Para erros críticos, interrompe imediatamentearia-atomic="true": Anuncia todo o conteúdo da notificação
Explicação: Dynamic notifications must be announced to screen readers without stealing focus. Use role='status' for info and role='alert' for errors.