Criando Popovers Acessíveis com CSS e JS Modernos

Popovers apresentam informações contextuais sem interromper o fluxo de trabalho do usuário—mas implementá-los de forma acessível continua sendo um desafio para muitos desenvolvedores. Seja modernizando código legado ou construindo uma biblioteca de componentes, entender a diferença entre popovers, tooltips e modais é crucial para criar a experiência de usuário adequada.
Este artigo aborda como construir popovers acessíveis usando CSS e JavaScript modernos, desde posicionamento dinâmico até navegação por teclado, explorando APIs nativas do navegador que reduzem a complexidade.
Pontos Principais
- Popovers exibem conteúdo rico e interativo que persiste até ser dispensado, diferente de tooltips que mostram dicas breves ao passar o mouse
- A API nativa Popover elimina a complexidade do JavaScript fornecendo recursos de acessibilidade integrados
- Gerenciamento adequado de foco e atributos ARIA são essenciais para navegação por teclado
- Posicionamento dinâmico garante que popovers permaneçam visíveis dentro dos limites da viewport
Entendendo Popovers vs. Tooltips vs. Modais
Tooltips fornecem dicas breves ao passar o mouse, tipicamente contendo uma única linha de texto. Eles desaparecem quando os usuários movem o cursor e não podem conter elementos interativos.
Popovers exibem conteúdo mais rico—cabeçalhos, parágrafos, botões ou formulários. Eles permanecem visíveis até serem explicitamente dispensados, permitindo que os usuários interajam tanto com o conteúdo do popover quanto com a página subjacente.
Modais criam uma experiência focada tornando o fundo inerte. Os usuários devem completar a interação modal antes de retornar ao conteúdo principal.
Requisitos Principais de Implementação
Posicionamento Dinâmico Dentro da Viewport
Popovers modernos devem se adaptar ao espaço disponível na tela. Quando um popover se estenderia além da borda da viewport, ele deve se reposicionar automaticamente:
const positionPopover = (trigger, popover) => {
const triggerRect = trigger.getBoundingClientRect()
const popoverRect = popover.getBoundingClientRect()
let top = triggerRect.bottom + 8
let left = triggerRect.left
// Flip to top if insufficient space below
if (top + popoverRect.height > window.innerHeight) {
top = triggerRect.top - popoverRect.height - 8
popover.classList.add('popover--top')
}
// Adjust horizontal position
if (left + popoverRect.width > window.innerWidth) {
left = window.innerWidth - popoverRect.width - 16
}
popover.style.top = `${top}px`
popover.style.left = `${left}px`
}
Alinhamento da Seta
O CSS cuida da seta visual que aponta para o elemento disparador:
.popover::after {
content: "";
position: absolute;
width: 12px;
height: 12px;
background: inherit;
border: inherit;
transform: rotate(45deg);
top: -7px;
left: 20px;
border-bottom: 0;
border-right: 0;
}
.popover--top::after {
top: auto;
bottom: -7px;
transform: rotate(225deg);
}
Mecanismos de Dispensa
Popovers acessíveis requerem múltiplos métodos de dispensa:
// Click outside
document.addEventListener('click', (e) => {
if (!popover.contains(e.target) && !trigger.contains(e.target)) {
closePopover()
}
})
// ESC key
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && isPopoverOpen) {
closePopover()
trigger.focus() // Return focus to trigger
}
})
Discover how at OpenReplay.com.
A API Nativa Popover
A API Popover elimina muito da complexidade do JavaScript:
<button popovertarget="my-popover">Open Info</button>
<div id="my-popover" popover>
<h3>Additional Information</h3>
<p>This popover requires no JavaScript for basic functionality.</p>
<button popovertarget="my-popover">Close</button>
</div>
Esta abordagem nativa lida com posicionamento, dispensa e gerenciamento de foco automaticamente. Para melhor acessibilidade, combine-a com o elemento <dialog>
:
<dialog id="enhanced-popover" popover>
<h2>Accessible Popover</h2>
<p>Combining dialog with popover provides semantic meaning.</p>
<button popovertarget="enhanced-popover">Close</button>
</dialog>
Comparando Soluções de Biblioteca vs. Nativas
Bibliotecas tradicionais como Popper.js oferecem algoritmos extensivos de posicionamento mas adicionam 15-30KB ao seu bundle. A API nativa Popover fornece:
- Zero JavaScript para funcionalidade básica
- Recursos de acessibilidade integrados
- Gerenciamento automático de foco
- Posicionamento otimizado pelo navegador
Para requisitos complexos de posicionamento, bibliotecas permanecem valiosas. Para casos de uso padrão, soluções nativas reduzem significativamente a complexidade.
Considerações Essenciais de Acessibilidade
Atributos ARIA
Ao construir popovers customizados sem a API nativa:
<button
aria-expanded="false"
aria-controls="custom-popover"
aria-haspopup="dialog">
Open Popover
</button>
<div
id="custom-popover"
role="dialog"
aria-labelledby="popover-title"
aria-modal="false">
<h2 id="popover-title">Popover Title</h2>
<!-- Content -->
</div>
Gerenciamento de Foco
Ordem adequada de foco garante que usuários de teclado possam navegar efetivamente:
const focusableElements = popover.querySelectorAll(
'a, button, input, textarea, select, [tabindex]:not([tabindex="-1"])'
)
// Trap focus within popover
popover.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
const firstElement = focusableElements[0]
const lastElement = focusableElements[focusableElements.length - 1]
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault()
lastElement.focus()
} else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault()
firstElement.focus()
}
}
})
Prevenindo Rolagem de Fundo
CSS sozinho pode prevenir rolagem de fundo ao usar a API nativa:
body:has(dialog[popover]:popover-open) {
overflow: hidden;
}
Conclusão
Construir popovers acessíveis requer equilibrar as necessidades do usuário com a implementação técnica. A API nativa Popover simplifica o desenvolvimento mantendo padrões de acessibilidade, embora soluções customizadas permaneçam necessárias para interações complexas.
Foque na navegação por teclado, implementação adequada de ARIA e padrões claros de dispensa. Seja usando APIs nativas ou construindo componentes customizados, a acessibilidade deve guiar suas decisões de implementação—garantindo que seus popovers funcionem para todos os usuários, independentemente de como eles interagem com sua interface.
Perguntas Frequentes
Use a API nativa Popover para implementações padrão, pois ela fornece acessibilidade integrada e não requer JavaScript. Escolha bibliotecas como Popper.js apenas quando precisar de lógica complexa de posicionamento ou precisar dar suporte a navegadores mais antigos que não possuem suporte à API nativa.
Tooltips mostram texto breve ao passar o mouse e desaparecem automaticamente, requerendo apenas rótulos ARIA simples. Popovers contêm elementos interativos, precisam de gerenciamento de foco, múltiplos métodos de dispensa e atributos ARIA adequados incluindo role dialog e aria-modal para garantir que leitores de tela os anunciem corretamente.
A API Popover tem suporte no Chrome 114+, Edge 114+ e Safari 17+. O suporte do Firefox está em desenvolvimento. Sempre verifique a compatibilidade atual do navegador e forneça fallbacks para navegadores não suportados usando detecção de recursos antes de implementar em produção.
Understand every bug
Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.