Dialog, Sheet e Popover: acessibilidade e gerenciamento de foco em componentes de sobreposição

Um cliente enviou um e-mail: “Quando a sobreposição do site abre e pressionamos Tab, o foco vai para a página ao fundo. Quem usa teclado simplesmente não consegue operá-la.”
O e-mail foi bem constrangedor, porque eu tinha acabado de implementar essa sobreposição na semana anterior.
Ao abrir o código, o problema ficou evidente: depois que o Dialog era aberto, o foco continuava no botão da página ao fundo. Naturalmente, ao pressionar Tab, ele seguia pela página de fundo. Para quem usa leitor de tela, a situação era ainda pior: essas pessoas nem sabiam que a sobreposição tinha sido aberta, pois o foco não entrava nela e os atributos ARIA também não estavam definidos.
Neste artigo, vamos falar sobre acessibilidade e gerenciamento de foco nos componentes Dialog, Sheet e Popover. Espero que os problemas que já enfrentei ajudem você a evitá-los.
Primeiro, entenda a diferença central entre os três componentes
Para ser sincero, muita gente — inclusive eu, no passado — não distingue com clareza esses três componentes. À primeira vista, todos parecem apenas sobreposições e soam praticamente iguais. Na realidade, porém, a diferença central entre eles determina como a acessibilidade deve ser tratada.
Dialog (caixa de diálogo modal)
Dialog é uma sobreposição modal que bloqueia completamente a interação com o fundo.
Por exemplo: você clica no botão “Excluir pedido” e aparece uma caixa de diálogo de confirmação. Nesse momento, uma camada cobre a página ao fundo e você não consegue clicar em nenhum elemento dela. Essa é a característica central do Dialog: obrigar a pessoa a lidar com a tarefa atual.
Casos de uso:
- avisos importantes (confirmação de exclusão, alertas de operação)
- preenchimento de formulários (login, cadastro)
- operações que exigem resposta imediata
Ponto essencial de acessibilidade: aria-modal="true" precisa estar definido e a armadilha de foco precisa ser implementada.
Sheet (painel lateral)
Sheet é um painel em formato de gaveta que desliza a partir de uma das bordas da tela. Em essência, funciona como Dialog: ambos são sobreposições modais, bloqueiam a interação com o fundo e precisam de uma armadilha de foco. A única diferença é visual: Sheet entra pela lateral, enquanto Dialog aparece no centro.
Casos de uso:
- menu de navegação (barra lateral em dispositivos móveis)
- painel de configurações (preferências, troca de tema)
- exibição de detalhes (detalhes de produto, prévia de artigo)
Para ser sincero, o termo Sheet também me pareceu estranho no começo. Depois percebi que é apenas outro nome para Drawer: algumas bibliotecas de UI usam Drawer, outras usam Sheet, e Radix UI e shadcn/ui adotam Sheet.
Ponto essencial de acessibilidade: assim como em Dialog, use aria-modal="true", armadilha de foco e fechamento com Esc.
Popover (caixa flutuante)
Popover é uma sobreposição não modal, que não bloqueia a interação com o fundo.
Esse ponto é fundamental: depois que Popover é aberto, a pessoa ainda pode clicar nos elementos do fundo, e o foco não fica obrigatoriamente restrito ao Popover.
Por exemplo: você clica no botão “Mais ações” e abre um pequeno painel com “Editar”, “Copiar” e “Excluir”. Isso é um Popover. Você pode clicar em outro botão do fundo, e o Popover se fecha automaticamente.
Casos de uso:
- menus suspensos (menus de ações, listas de opções)
- dicas de ferramenta (explicações ricas, instruções de uso)
- ações rápidas (editar, copiar, excluir)
Ponto essencial de acessibilidade: aria-modal="false" (ou omitido), sem armadilha de foco obrigatória e fechamento ao clicar fora.
Comparação rápida
Para ser sincero, até eu consultei esta tabela enquanto a escrevia, pois antes alguns conceitos ainda não estavam claros para mim.
| Característica | Dialog | Sheet | Popover |
|---|---|---|---|
| Bloqueia o fundo | ✅ deve bloquear | ✅ deve bloquear | ❌ não bloqueia |
| Armadilha de foco | obrigatória | obrigatória | opcional (recomenda-se não forçar) |
| Fechamento com Esc | obrigatório | obrigatório | recomendado |
| Fechamento ao clicar fora | opcional | opcional | comportamento padrão |
| Papel ARIA | dialog | dialog | popover |
aria-modal | "true" | "true" | "false" ou omitido |
| Posição visual | centralizado | desliza pela lateral | posicionado em relação ao acionador |
Em uma frase: Dialog e Sheet são sobreposições modais; Popover é uma sobreposição não modal. Sobreposições modais precisam implementar uma armadilha de foco; as não modais não precisam obrigatoriamente fazê-lo.
Padrões de acessibilidade WCAG em detalhes
Para ser sincero, os padrões WCAG parecem bem tediosos no começo. Há muitos termos em inglês, e a leitura lembra um texto jurídico. Mas, depois de enfrentar problemas em um projeto real, percebi como esses padrões são úteis: não servem apenas para passar em uma auditoria, e sim para permitir que as pessoas realmente usem a interface.
Atributos ARIA obrigatórios
Há três atributos ARIA essenciais para componentes de sobreposição:
1. role="dialog"
Esse atributo informa às tecnologias assistivas, como leitores de tela, que o elemento é uma caixa de diálogo.
<div role="dialog">
<!-- Conteúdo da sobreposição -->
</div>
2. aria-labelledby
Esse atributo associa o título da sobreposição ao elemento. Quando ela é aberta, o leitor de tela anuncia primeiro o título.
<div role="dialog" aria-labelledby="dialog-title">
<h2 id="dialog-title">Confirmar exclusão</h2>
<p>Esta ação não pode ser desfeita.</p>
</div>
3. aria-modal="true" (somente para sobreposições modais)
Esse atributo informa ao leitor de tela que o conteúdo ao fundo não está acessível.
<div role="dialog" aria-modal="true">
<!-- Conteúdo da sobreposição modal -->
</div>
Para ser sincero, eu me esquecia bastante de aria-labelledby. Só quando testei com um leitor de tela percebi o problema: sem esse atributo, ao abrir a sobreposição a pessoa não ouve nada e não sabe do que se trata.
Requisitos de navegação por teclado
A WCAG estabelece requisitos claros para a navegação por teclado em sobreposições:
Tecla Tab: fazer o foco circular dentro da sobreposição
Quando a pessoa pressiona Tab, o foco deve circular entre os elementos interativos da sobreposição, sem escapar para a página ao fundo.
Shift+Tab: fazer o foco circular no sentido inverso
Ao pressionar Shift+Tab, o foco deve percorrer os elementos na ordem inversa.
Tecla Esc: fechar a sobreposição
Quando a pessoa pressiona Esc, a sobreposição deve ser fechada. Isso é obrigatório: algumas pessoas estão acostumadas a fechar sobreposições com Esc e, se não houver suporte, ficam presas dentro delas.
Enter/Space: acionar botões
Essas duas teclas servem para ativar botões ou links.
Para ser sincero, já tive problemas com o ciclo do Tab. Quando a sobreposição abria, o foco não ficava limitado a ela e, ao pressionar Tab, ia para a página ao fundo. Esse foi justamente o problema relatado pelo cliente no início do artigo.
Regras de gerenciamento de foco
O gerenciamento de foco é a parte da acessibilidade de sobreposições mais fácil de esquecer. Os requisitos da WCAG são simples:
Ao abrir a sobreposição:
o foco deve ir para o primeiro elemento interativo dentro dela, normalmente o botão de fechar ou o primeiro campo.
Ao fechar a sobreposição:
o foco deve voltar ao elemento acionador, ou seja, ao botão que abriu a sobreposição.
Para ser sincero, antes eu nem percebia que era preciso restaurar o foco após o fechamento. Só notei ao testar com o teclado: depois que a sobreposição fechava, o foco desaparecia e a pessoa precisava encontrá-lo novamente. A experiência era realmente ruim.
Caso especial:
se a sobreposição tiver um aviso importante, como instruções de uma operação, o foco deve ir primeiro para o elemento contêiner. Assim, o leitor de tela anuncia o aviso antes de a pessoa interagir com os controles.
Para isso, adicione tabindex="0" ao contêiner:
<div role="dialog" aria-modal="true" tabindex="0">
<h2>Instruções da operação</h2>
<p>Leia com atenção o conteúdo a seguir antes de continuar...</p>
<button>Confirmar</button>
</div>
Quando a sobreposição abre, o foco vai primeiro para o contêiner. O leitor de tela anuncia todo o conteúdo e, depois, a pessoa pode usar Tab para chegar ao botão.
Como funciona uma armadilha de foco
Para ser sincero, “armadilha de foco” parece algo complexo, mas o princípio é bem simples: fazer a tecla Tab circular dentro da sobreposição.
O que é uma armadilha de foco
Definição de armadilha de foco: limitar a navegação por Tab a uma área específica, fazendo o foco circular dentro dela.
Por exemplo: depois que a sobreposição abre, a pessoa pressiona Tab e o foco vai do botão “Fechar” para o botão “Confirmar”. Ao pressionar Tab novamente, ele volta para “Fechar”. Isso é uma armadilha de foco.
Ela é necessária para impedir que a pessoa acione por engano o conteúdo ao fundo. Se o foco conseguir chegar à página atrás da sobreposição, um botão do fundo pode ser acionado involuntariamente e causar uma operação inesperada.
Lógica de implementação em JavaScript
A lógica central de uma armadilha de foco é simples: localizar todos os elementos interativos da sobreposição, ouvir a tecla Tab e fazer o foco circular entre o primeiro e o último elemento.
function trapFocus(modal) {
// Localiza todos os elementos interativos
const focusableElements = modal.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstElement = focusableElements[0];
const lastElement = focusableElements[focusableElements.length - 1];
// Ouve eventos de teclado
modal.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
// Shift+Tab: no primeiro elemento, volta para o último
if (e.shiftKey && document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
}
// Tab: no último elemento, volta para o primeiro
else if (!e.shiftKey && document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
// Esc fecha a sobreposição
if (e.key === 'Escape') {
closeModal();
}
});
}
Para ser sincero, precisei reescrever esse trecho várias vezes até funcionar. Os principais problemas foram:
- o seletor de
focusableElementsprecisa estar completo; se faltar algum tipo de elemento, o foco pode escapar - é obrigatório chamar
e.preventDefault(), caso contrário o comportamento padrão do navegador leva o foco para fora
Conhecendo a biblioteca focus-trap
Se você não quiser implementar uma armadilha de foco por conta própria, pode usar uma biblioteca pronta: focus-trap-react.
import FocusTrap from 'focus-trap-react';
<FocusTrap>
<div className="modal">
<button>Fechar</button>
<button>Confirmar</button>
</div>
</FocusTrap>
Essa biblioteca cuida automaticamente do ciclo de foco, do fechamento com Esc e de sobreposições em várias camadas.
Para ser sincero, hoje quase não uso mais essa biblioteca, porque o shadcn/ui já incorpora internamente o gerenciamento de foco. O Radix UI, que serve de base para o shadcn/ui, trata toda a lógica da armadilha de foco sem exigir uma dependência adicional.
Na prática com shadcn/ui: implementando Dialog
Para ser sincero, depois que comecei a usar shadcn/ui, nunca mais implementei um componente de sobreposição do zero. Não é por preguiça: componentes escritos manualmente sempre acabam apresentando algum problema de acessibilidade, enquanto o shadcn/ui, baseado no Radix UI, cuida automaticamente desses detalhes.
Instalação e uso básico
npx shadcn@latest add dialog
Depois da instalação, o arquivo components/ui/dialog.tsx é gerado automaticamente.
Exemplo completo de código
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
export function DeleteConfirmDialog() {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Excluir pedido</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Confirmar exclusão</DialogTitle>
<DialogDescription>
Esta ação não pode ser desfeita. Tem certeza de que deseja excluir este pedido?
</DialogDescription>
</DialogHeader>
<div className="flex justify-end gap-2 mt-4">
<Button variant="outline">Cancelar</Button>
<Button variant="destructive">Excluir</Button>
</div>
</DialogContent>
</Dialog>
)
}
Para ser sincero, o código parece simples, mas o Radix UI cuida de muitos detalhes nos bastidores:
- ao abrir a sobreposição, o foco vai para o primeiro botão (“Cancelar”)
- ao fechá-la, o foco volta ao botão “Excluir pedido”
- a tecla Tab circula dentro da sobreposição
- a tecla Esc fecha a sobreposição
aria-labelledbyé associado automaticamente aoDialogTitlearia-describedbyé associado automaticamente aoDialogDescription
Principais recursos de acessibilidade
1. Gerenciamento automático de foco
Quando o Dialog do Radix UI é aberto, o foco vai automaticamente para o primeiro elemento interativo da sobreposição. Quando ele é fechado, o foco volta ao elemento acionador.
2. Associação automática dos atributos ARIA
DialogTitle é associado automaticamente a aria-labelledby, e DialogDescription, a aria-describedby.
<!-- HTML gerado pelo Radix UI -->
<div role="dialog" aria-modal="true" aria-labelledby="radix-:r1:" aria-describedby="radix-:r2:">
<h2 id="radix-:r1:">Confirmar exclusão</h2>
<p id="radix-:r2:">Esta ação não pode ser desfeita...</p>
</div>
Para ser sincero, é muito fácil esquecer esses detalhes ao implementar o componente manualmente. Com shadcn/ui, você não precisa se preocupar com eles.
3. Fechamento automático com Esc
Ao pressionar Esc, a sobreposição é fechada automaticamente e o foco volta ao elemento acionador.
4. Fechamento ao clicar na camada de fundo
Clicar na camada de fundo, a área cinza fora da sobreposição, também a fecha. Esse comportamento pode ser bloqueado com a propriedade onInteractOutside de DialogContent.
<DialogContent onInteractOutside={(e) => e.preventDefault()}>
<!-- Clicar na camada de fundo não fecha a sobreposição -->
</DialogContent>
Na prática com shadcn/ui: implementando Sheet
Sheet tem exatamente os mesmos recursos de acessibilidade de Dialog. A única diferença é visual: Sheet desliza a partir de uma das laterais.
Instalação e uso básico
npx shadcn@latest add sheet
Exemplo completo de código
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet"
import { Button } from "@/components/ui/button"
export function NavigationSheet() {
return (
<Sheet>
<SheetTrigger asChild>
<Button variant="outline">Abrir menu</Button>
</SheetTrigger>
<SheetContent side="left">
<SheetHeader>
<SheetTitle>Menu de navegação</SheetTitle>
<SheetDescription>
Escolha a página que deseja acessar
</SheetDescription>
</SheetHeader>
<nav className="flex flex-col gap-4 mt-4">
<a href="/" className="hover:underline">Início</a>
<a href="/about" className="hover:underline">Sobre</a>
<a href="/contact" className="hover:underline">Contato</a>
</nav>
</SheetContent>
</Sheet>
)
}
Diferenças em relação ao Dialog
Para ser sincero, os códigos de Sheet e Dialog são quase idênticos; só mudam os nomes dos componentes. As principais diferenças são:
1. Animação de entrada pela lateral
Por padrão, Sheet desliza da direita para dentro. Você pode controlar a direção com a propriedade side:
<SheetContent side="left"> <!-- entra pela esquerda -->
<SheetContent side="right"> <!-- entra pela direita (padrão) -->
<SheetContent side="top"> <!-- entra por cima -->
<SheetContent side="bottom"> <!-- entra por baixo -->
2. Os recursos de acessibilidade são os mesmos
Sheet tem exatamente os mesmos recursos de acessibilidade de Dialog:
role="dialog"aria-modal="true"- armadilha de foco, fechamento com Esc e restauração do foco
Para ser sincero, uso Sheet principalmente em menus de navegação para dispositivos móveis. O efeito de entrada pela lateral combina melhor com os padrões de interação mobile.
Na prática com shadcn/ui: implementando Popover
Popover é uma sobreposição não modal. A diferença central em relação a Dialog e Sheet é: ele não bloqueia a interação com o fundo.
Instalação e uso básico
npx shadcn@latest add popover
Exemplo completo de código
import {
Popover,
PopoverContent,
PopoverHeader,
PopoverTitle,
PopoverDescription,
PopoverTrigger,
} from "@/components/ui/popover"
import { Button } from "@/components/ui/button"
export function ActionPopover() {
return (
<Popover>
<PopoverTrigger asChild>
<Button variant="outline">Mais ações</Button>
</PopoverTrigger>
<PopoverContent>
<PopoverHeader>
<PopoverTitle>Ações rápidas</PopoverTitle>
<PopoverDescription>
Escolha uma das ações a seguir
</PopoverDescription>
</PopoverHeader>
<div className="flex flex-col gap-2 mt-2">
<Button size="sm">Editar</Button>
<Button size="sm">Copiar</Button>
<Button size="sm" variant="destructive">Excluir</Button>
</div>
</PopoverContent>
</Popover>
)
}
Diferenças principais
Para ser sincero, o código de Popover é parecido com o de Dialog e Sheet, mas o comportamento por trás dele é completamente diferente:
1. Não modal
Depois que Popover é aberto, a pessoa ainda pode clicar nos elementos do fundo. O foco não fica obrigatoriamente limitado ao Popover.
2. Foco não restrito
Ao pressionar Tab, o foco pode sair do Popover e chegar aos elementos do fundo. Nesse ponto, ele é totalmente diferente de Dialog.
3. Fechamento ao clicar fora
Ao clicar em qualquer elemento fora do Popover, ele é fechado automaticamente. Esse é o comportamento padrão, que pode ser bloqueado com a propriedade onInteractOutside.
<PopoverContent onInteractOutside={(e) => e.preventDefault()}>
<!-- Clicar fora não fecha o Popover -->
</PopoverContent>
4. Posicionamento flexível
Popover controla o alinhamento horizontal com a propriedade align:
<PopoverContent align="start"> <!-- alinhado à esquerda -->
<PopoverContent align="center"> <!-- centralizado (padrão) -->
<PopoverContent align="end"> <!-- alinhado à direita -->
Para ser sincero, uso Popover principalmente em menus de ações: ao clicar em um botão, aparecem algumas opções rápidas. Esse cenário não precisa bloquear a interação com o fundo, então Popover é a escolha ideal.
Técnicas avançadas e problemas comuns
Para ser sincero, já enfrentei muitos problemas com componentes de sobreposição. Aqui estão alguns dos mais comuns.
Problema na restauração do foco: o elemento acionador foi removido
Cenário: depois que a sobreposição é aberta, o elemento acionador, como um botão, é removido. Ao fechar, não há para onde devolver o foco.
Soluções:
- não remova o elemento acionador; apenas o oculte
- ou registre um elemento de destino para receber o foco
const [triggerElement, setTriggerElement] = useState<HTMLElement | null>(null);
// Registra o elemento acionador ao abrir a sobreposição
const handleOpen = (e: React.MouseEvent<HTMLButtonElement>) => {
setTriggerElement(e.currentTarget);
setOpen(true);
};
// Devolve o foco ao fechar a sobreposição
const handleClose = () => {
setOpen(false);
triggerElement?.focus();
};
Para ser sincero, já enfrentei esse problema. Depois que a pessoa excluía um registro, a sobreposição fechava e o foco desaparecia. Resolvi o caso fazendo o foco voltar para o registro anterior da lista.
Problema com leitores de tela: o conteúdo da sobreposição não é anunciado
Cenário: depois que a sobreposição abre, o leitor de tela não anuncia seu conteúdo, e a pessoa não sabe o que há nela.
Causas:
- ausência do atributo
aria-labelledby - o foco não foi movido para dentro da sobreposição
Solução:
defina tanto DialogTitle quanto DialogDescription. O shadcn/ui associa automaticamente os atributos ARIA.
<DialogContent>
<DialogHeader>
<DialogTitle>Confirmar exclusão</DialogTitle> <!-- obrigatório -->
<DialogDescription>Esta ação não pode ser desfeita</DialogDescription> <!-- obrigatório -->
</DialogHeader>
</DialogContent>
Para ser sincero, antes eu frequentemente me esquecia de DialogDescription. Só percebi o efeito ao testar com o NVDA, um leitor de tela: sem a descrição, a pessoa conhece apenas o título da sobreposição, mas não sabe o conteúdo específico.
Problema com sobreposições aninhadas: gerenciamento de foco confuso
Cenário: a sobreposição A abre a sobreposição B. Depois que B é fechada, o foco vai para um lugar inesperado.
Solução:
Dialog e Sheet do Radix UI oferecem suporte a aninhamento. Quando a sobreposição interna é fechada, o foco volta ao acionador interno, que pode ser um botão dentro da sobreposição externa.
<Dialog>
<DialogTrigger>Abrir sobreposição A</DialogTrigger>
<DialogContent>
<DialogTitle>Sobreposição A</DialogTitle>
<!-- Abre a sobreposição B dentro da sobreposição A -->
<Dialog>
<DialogTrigger>Abrir sobreposição B</DialogTrigger>
<DialogContent>
<DialogTitle>Sobreposição B</DialogTitle>
</DialogContent>
</Dialog>
</DialogContent>
</Dialog>
Para ser sincero, tento evitar sobreposições em várias camadas. Quando elas são realmente necessárias, uso o suporte a aninhamento do Radix UI para que a biblioteca cuide automaticamente do foco.
Problema com atraso de animação: o foco fica fora da sobreposição
Cenário: a sobreposição tem uma animação, como um fade-in, e durante essa animação o foco não está dentro dela.
Causa:
no início da animação, a sobreposição ainda não está totalmente visível, e a tentativa de definir o foco falha.
Solução:
o Radix UI cuida desse problema automaticamente. O foco só é definido depois que a animação termina.
Se você estiver implementando o componente por conta própria, espere a animação terminar:
modal.addEventListener('animationend', () => {
const firstFocusable = modal.querySelector('button, [href], input');
firstFocusable?.focus();
});
Para ser sincero, já enfrentei esse problema. Em uma sobreposição feita manualmente, o foco continuava no botão do fundo porque eu tentava defini-lo antes de a animação terminar. Só resolvi ao adicionar um ouvinte para o evento animationend.
Conclusão
Depois de tudo isso, os pontos centrais se resumem a três:
1. A diferença essencial entre os três componentes
Dialog e Sheet são sobreposições modais: bloqueiam a interação com o fundo e exigem uma armadilha de foco.
Popover é uma sobreposição não modal: não bloqueia a interação com o fundo e não restringe obrigatoriamente o foco.
2. Os três principais requisitos de acessibilidade da WCAG
Atributos ARIA (role="dialog", aria-labelledby, aria-modal="true")
Navegação por teclado (ciclo com Tab, movimento inverso com Shift+Tab e fechamento com Esc)
Gerenciamento de foco (focar ao abrir e restaurar ao fechar)
3. O shadcn/ui cuida automaticamente de todos os detalhes
O Radix UI cuida automaticamente da armadilha de foco, dos atributos ARIA e da navegação por teclado. Com shadcn/ui, você praticamente não precisa se preocupar com problemas de acessibilidade.
Para ser sincero, depois de implementar tantos componentes de sobreposição, minha regra hoje é simples: em produção, prefira shadcn/ui. Componentes escritos manualmente costumam acumular problemas de acessibilidade, enquanto o shadcn/ui, baseado no Radix UI, já trata esses detalhes.
Só há um cuidado indispensável: entenda o princípio. Quando você sabe o que o Radix UI faz nos bastidores, consegue localizar rapidamente a causa de qualquer problema.
Referências
- Papel dialog do WAI-ARIA — MDN
- Acessibilidade no Radix UI
- Referência rápida da WCAG 2.1
- Como dominar modais acessíveis
- focus-trap-react
FAQ
Qual é a diferença entre Dialog, Sheet e Popover?
Quais requisitos de acessibilidade um componente de sobreposição precisa atender?
O que é uma armadilha de foco e por que uma sobreposição modal precisa implementá-la?
Quais detalhes de acessibilidade o componente Dialog do shadcn/ui trata automaticamente?
• ao abrir a sobreposição, o foco vai para o primeiro elemento interativo
• ao fechar, o foco volta ao elemento acionador
• a tecla Tab circula dentro da sobreposição
• a tecla Esc fecha a sobreposição
• aria-labelledby é associado automaticamente ao DialogTitle
• aria-describedby é associado automaticamente ao DialogDescription
Para onde o foco deve voltar depois que a sobreposição é fechada?
O que fazer quando uma animação impede a definição do foco?
Como fazer o leitor de tela anunciar o conteúdo da sobreposição?
18 min de leitura · Publicado em: 29 mar 2026 · Atualizado em: 4 set 2026
Tailwind e shadcn/ui na prática
Se você chegou pela busca, o caminho mais rápido é ir para o post anterior ou próximo desta série.
Anterior
shadcn/ui e Radix: como manter a acessibilidade ao personalizar componentes
O shadcn/ui usa Radix Primitives como base. Entenda como preservar a acessibilidade ao personalizar componentes, usar asChild, gerenciar o foco e manter os atributos ARIA para não comprometer a navegação por teclado.
Parte 9 de 14
Próximo
Otimização de desempenho do Tailwind: JIT, configuração de content e controle do tamanho em produção
Entenda como funciona o modo JIT do Tailwind CSS, as melhores práticas para configurar content e uma estratégia de otimização em quatro camadas para reduzir o tamanho em produção, com casos práticos e uma análise dos novos recursos do Tailwind v4.
Parte 11 de 14



Comentários
Entre com GitHub para comentar