Back

Criando Popovers Acessíveis com CSS e JS Modernos

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
  }
})

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.

OpenReplay