A navegação por teclado é essencial para:
A ordem de tabulação deve seguir a ordem lógica do conteúdo:
<!-- ✅ Ordem lógica -->
<form>
<label for="nome">Nome</label>
<input type="text" id="nome">
<label for="email">Email</label>
<input type="email" id="email">
<button type="submit">Enviar</button>
</form>
<!-- ❌ Ruim - tabindex positivo quebra a ordem natural -->
<button tabindex="1">Botão 1</button>
<button tabindex="2">Botão 2</button>
<button tabindex="3">Botão 3</button>
<!-- ✅ Bom - ordem natural -->
<button>Botão 1</button>
<button>Botão 2</button>
<button>Botão 3</button>
Use tabindex="-1" para remover elementos interativos da ordem de tabulação
quando necessário (mas ainda permita acesso programático):
<!-- Elemento removido da ordem de tab, mas acessível via JavaScript -->
<div tabindex="-1" id="modal">Conteúdo do modal</div>
Sempre torne o foco claramente visível:
/* ✅ Estilo de foco visível */
button:focus,
a:focus,
input:focus {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
/* Para navegadores modernos */
button:focus-visible {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
<!-- ❌ Ruim -->
<div onclick="handleClick()">Clique aqui</div>
<!-- ✅ Bom -->
<button onclick="handleClick()">Clique aqui</button>
Se precisar criar elementos interativos customizados:
<!-- Div que funciona como botão -->
<div
role="button"
tabindex="0"
aria-label="Fechar modal"
onclick="closeModal()"
onkeydown="handleKeyDown(event)">
Fechar
</div>
function handleKeyDown(event) {
// Ativar com Enter ou Espaço
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
closeModal();
}
}
Quando abrir um modal, mova o foco para dentro:
function openModal() {
const modal = document.getElementById('modal');
modal.style.display = 'block';
// Mover foco para o modal
const firstFocusable = modal.querySelector('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
if (firstFocusable) {
firstFocusable.focus();
}
// Prevenir tabulação fora do modal
trapFocus(modal);
}
function trapFocus(element) {
const focusableElements = element.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
element.addEventListener('keydown', function(e) {
if (e.key === 'Tab') {
if (e.shiftKey) {
// Shift + Tab
if (document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
}
} else {
// Tab
if (document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
}
// ESC para fechar
if (e.key === 'Escape') {
closeModal();
}
});
}
Ao fechar um modal, retorne o foco para o elemento que o abriu:
let previousFocus = null;
function openModal() {
previousFocus = document.activeElement;
// ... abrir modal e mover foco
}
function closeModal() {
// ... fechar modal
if (previousFocus) {
previousFocus.focus();
previousFocus = null;
}
}
document.addEventListener('keydown', function(e) {
// Ctrl/Cmd + K para busca
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
openSearch();
}
// ESC para fechar modais
if (e.key === 'Escape') {
closeAllModals();
}
});
<button
aria-keyshortcuts="Ctrl+K"
onclick="openSearch()">
Buscar (Ctrl+K)
</button>
Adicione links para pular conteúdo repetitivo:
<a href="#main-content" class="skip-link">
Pular para conteúdo principal
</a>
<header>
<!-- Navegação -->
</header>
<main id="main-content">
<!-- Conteúdo principal -->
</main>
.skip-link {
position: absolute;
top: -40px;
left: 0;
background: #000;
color: #fff;
padding: 8px;
text-decoration: none;
z-index: 100;
}
.skip-link:focus {
top: 0;
}
<nav aria-label="Menu principal">
<ul>
<li>
<button
aria-expanded="false"
aria-haspopup="true"
onclick="toggleMenu(this)">
Produtos
</button>
<ul role="menu" hidden>
<li><a href="/produtos/1" role="menuitem">Produto 1</a></li>
<li><a href="/produtos/2" role="menuitem">Produto 2</a></li>
</ul>
</li>
</ul>
</nav>
function toggleMenu(button) {
const menu = button.nextElementSibling;
const isExpanded = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', !isExpanded);
menu.hidden = isExpanded;
if (!isExpanded) {
menu.querySelector('a').focus();
}
}
// Fechar com ESC
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
const openMenu = document.querySelector('[aria-expanded="true"]');
if (openMenu) {
toggleMenu(openMenu);
openMenu.focus();
}
}
});