Frontends com Vibecoding · Capítulo 8 de 33 · 29 min
Projetar menus, popovers e modais acessíveis
Menu, popover e modal passam a funcionar pelo teclado, com foco visível, fechamento previsível e recurso nativo do navegador antes de qualquer biblioteca.
Este capítulo faz parte do curso gratuito Frontends com Vibecoding.
A palavra menu tem quatro sentidos, e trocar um pelo outro no código custa visitante. Navegação é só um deles: a lista de links que leva de uma página do site a outra, como o índice de um catálogo.
Quando essa lista recebe o papel de menu de programa (role="menu"), quem usa leitor de tela ouve instrução de setas. Ali ela não funciona, porque a lista só responde ao Tab, e a pessoa fecha a página sem avisar ninguém.
Os outros três sentidos pedem código diferente. Editar ou excluir um item pede menu de ação, com teclado próprio. Mostrar e esconder um bloco pede um botão que declara se está aberto. A barra fixa de programa de computador quase nunca cabe num site. O agente, a ferramenta de IA que escreve o código, confunde os quatro.
Troque todo rótulo genérico por um que diz o destino, porque a pessoa decide clicar pela promessa que lê. Em vez de Continuar, escreva Criar conta grátis; em vez de Enviar, escreva Baixar o PDF.
Essa pista que o link dá antes do clique tem nome: aroma de informação. Rótulo vago é a forma mais barata de perder quem já estava quase comprando.
Se a figura ultrapassar a área visível, deslize para os lados. Pelo teclado, foque a figura e use as setas.
Escolha o propósito do menu primeiro e a forma depois, pelo número de itens, como no quadro abaixo. A barra do topo aguenta de cinco a sete links e a lista suspensa até oito itens por categoria.
O mega menu, painel largo de várias colunas, entra acima de 50 páginas. Um catálogo de 40 categorias já passa desse limite, e a barra sozinha vira fila cortada no celular.
As formas de navegação, o volume que cada uma aguenta e o preço que cobra
| Forma | Quando ela cabe | O preço que ela cobra |
|---|---|---|
| Barra horizontal no topo | Navegação principal, de cinco a sete links | Alvo de toque de 44 px; não pode sumir ao rolar |
| Lista suspensa | Categoria com até oito itens | Não aninhe além de dois níveis: o terceiro ninguém encontra |
| Mega menu | Site com mais de 50 páginas | Precisa de atraso ao passar o mouse, senão fecha antes da hora |
| Gaveta com ícone de três traços | Navegação secundária no celular | Esconde demais; ação frequente não pode morar só aí dentro |
| Barra inferior no celular | De três a cinco seções principais | Fica na zona do polegar; cada item com 44 px ou mais |
| Barra lateral vertical | Painel e produto por assinatura | Precisa de modo estreito recolhido e grupos com nome claro |
Escolha primeiro o propósito do menu; só depois escolha a forma nesta tabela, pelo volume de itens.
Fonte: Quadro do autor, com os padrões de acessibilidade seguindo a norma internacional de interface.
Há medida oficial para o clique no menu: o INP, o tempo entre o toque da pessoa e a resposta que aparece na tela. Ele é um dos Core Web Vitals, as notas de experiência que o Google acompanha em toda página.
A faixa boa vai até 200 milissegundos, apurados nas visitas reais e separados entre celular e computador. Menu que demora a abrir derruba a página nessa nota, e a causa quase sempre é código demais rodando no clique.
Um pedido simples melhora o resto: mandar o navegador buscar a próxima página antes do clique, pela Speculation Rules API. Vale para os links do menu principal. Link que executa ação, como sair da conta, fica de fora, porque o pré-carregamento dispararia a ação sem ninguém clicar.
Aba aberta, filtro aplicado e número da página precisam viver no endereço da página (a URL), nunca só na memória da tela. É o endereço que a cliente copia para o WhatsApp, e é dele que o botão de voltar depende. Fora da URL, o link chega vazio do outro lado e o recarregamento apaga a escolha.
Em React, a base mais usada para montar telas em pedaços, o pacote nuqs faz essa ligação pronta. Sem React, um formulário que envia pela URL resolve igual.
A rolagem infinita, a lista que cresce sozinha enquanto a pessoa desce a tela, quebra as três garantias de uma vez. Sem número de página no endereço, o Google indexa só a primeira carga e quem navega pelo teclado nunca alcança o rodapé. Prefira paginação ou um botão de carregar mais que grave a página na URL.
Se a figura ultrapassar a área visível, deslize para os lados. Pelo teclado, foque a figura e use as setas.
O menu de ação responde a cinco movimentos de teclado, e o agente costuma entregar só o clique de mouse. As setas movem o foco, que é o destaque mostrando qual item está selecionado, e voltam ao primeiro item depois do último.
Home e End saltam para as pontas e uma letra pula para o item que começa com ela. Enter aciona, e Esc fecha devolvendo o foco ao botão que abriu. O defeito fica invisível até alguém testar sem encostar no mouse.
Se a figura ultrapassar a área visível, deslize para os lados. Pelo teclado, foque a figura e use as setas.
Quatro requisitos fecham o menu de celular. A gaveta que abre pelo ícone de três traços prende o foco do teclado dentro do painel. Ela fecha com Esc ou com um toque fora e devolve o foco ao botão que a abriu.
Ação usada toda semana não pode morar só dentro dessa gaveta, porque quem não vê a lista esquece que ela existe. Uma barra inferior com três a cinco seções, na faixa que o polegar alcança sem trocar a mão de posição, resolve isso.
Cada item obedece à régua de área de toque, o retângulo que responde ao dedo e que é maior que o desenho do ícone. O piso é 24 por 24 pixels, critério 2.5.8 da WCAG 2.2, a norma internacional de acessibilidade. A Apple pede 44 e o Android pede 48. Peça 48 e passa nas três.
Se a figura ultrapassar a área visível, deslize para os lados. Pelo teclado, foque a figura e use as setas.
Conte os itens da navegação principal e escolha a forma pelo quadro acima. Acima de sete links na barra, agrupe categorias ou peça mega menu; no celular, leve as três a cinco seções mais usadas para a barra inferior.
Depois leia cada rótulo isolado do resto da tela, porque é assim que ele chega a quem chegou de fora. Ver planos e preços nomeia o destino; Saiba mais não diz nada sozinho. Peça a lista de links, a medida do INP e o pré-carregamento num pedido só, deixando de fora o link que executa ação.
Percorra então o menu de ação e a gaveta sem tocar no mouse. Chegue ao botão pelo Tab, abra com Enter, cicle com as setas e feche com Esc. Confira que o foco volta ao botão de origem.
Por fim, aplique um filtro numa categoria e cole o endereço numa aba anônima. Se a tela não mostrar o mesmo resultado, o link que a cliente compartilha chega vazio, e é o que consertar antes de aprovar a entrega.
Abra menu, caixinha e janela com o que o navegador já traz
A forma de menu e o estado guardado na URL estão resolvidos, e o comportamento de abrir, ancorar e fechar ainda pode vir do navegador, sem script.
Menu suspenso, popover, janela modal e carrossel já vêm prontos no navegador e deixaram de exigir programação própria. Popover é a caixinha que abre presa a um botão, e carrossel é a faixa de imagens que desliza para o lado.
O navegador entrega abertura, fechamento ao clicar fora, posicionamento e indicadores por atributo de HTML e regra de CSS. Cada script que a empresa deixa de manter é uma fonte de defeito a menos. O ganho aparece quando você revisa o que o agente de IA entregou pronto.
Se a figura ultrapassar a área visível, deslize para os lados. Pelo teclado, foque a figura e use as setas.
Três atributos substituem o script de abrir e fechar. O popovertarget num botão abre a caixinha, e o clique fora fecha sozinho.
O elemento dialog com closedby faz o mesmo com a janela modal, aquela que cobre a tela e exige uma resposta. O par commandfor e command, da Invoker Commands API, abre e fecha pelo atributo.
Consulte na tabela adiante o suporte de cada recurso, junto com o que acontece onde ele ainda não chegou. Onde o comando por atributo falta, o botão fica parado, e aí entra o único script aceitável: o mínimo que abre e fecha a janela.
A armadilha é a janela sem botão de fechar visível. Quem usa leitor de tela fica preso dentro dela, e nenhum atributo novo conserta isso.
Se a figura ultrapassar a área visível, deslize para os lados. Pelo teclado, foque a figura e use as setas.
:has() é o seletor que faz o elemento de fora reagir ao que existe dentro dele. É ele que marca o item ativo do menu sem script: a barra muda quando um link avisa que representa a página atual. Onde ele falta, os itens seguem legíveis e clicáveis, só sem o destaque.
O anchor positioning, o posicionamento ancorado nativo, prende a caixa ao botão que a abriu, inclusive o painel largo de um mega menu. Sem ele, a caixa abre no centro da tela e continua utilizável.
Peça o Floating UI, a biblioteca que calcula essa posição por JavaScript. Ela entra só enquanto o painel de visitas mostrar muito navegador antigo demais para a âncora nativa.
A lista suspensa de opções com visual próprio, o customizable select, ainda está em desenvolvimento. Enquanto ela não chega, o agente reimplementa a lista à mão, e daí nasce a maior parte do script de menu que você vai manter depois.
O plano B mais barato é o select nativo sem enfeite, que já é acessível e sobe o teclado certo no celular.
Carrossel simples também dispensa programação. Os pseudoelementos ::scroll-marker desenham os pontinhos de posição a partir da própria rolagem, o movimento de arrastar a faixa para o lado. Sem suporte, a faixa rola por toque, só sem os pontinhos.
Se a figura ultrapassar a área visível, deslize para os lados. Pelo teclado, foque a figura e use as setas.
Quando o carrossel precisa de mais, o Embla resolve com cerca de 4 kB. O Swiper pesa dez vezes isso e só entra quando o efeito 3D pronto justifica o peso.
A animação de entrada também é nativa. O @starting-style anima a abertura da caixinha e da janela. A View Transitions same-document, a troca animada entre dois estados da mesma página, vem antes de qualquer biblioteca. Sem suporte, a troca acontece seca e ninguém perde conteúdo.
O critério de decisão é o navegador do seu público, lido no painel de visitas do site. Use o recurso nativo quando a maioria do acesso vem de navegador atualizado e o plano B da tabela ainda cumpre a função. Reserve a biblioteca para onde esse plano B quebra.
Papel de menu de programa numa navegação simples continua errado, com ou sem script.
Cada recurso nativo, onde já roda e o que acontece sem suporte
| Recurso | Navegador | Plano B sem suporte |
|---|---|---|
| :has() no item ativo | Baseline Widely Available desde junho de 2026 | Itens legíveis e clicáveis, sem o destaque automático |
| Anchor positioning | Baseline Newly Available em 13 de janeiro de 2026; Firefox 147 fechou o conjunto | Caixa abre no centro da tela; Floating UI enquanto o público não cobre |
| dialog, closedby e Invoker Commands | dialog Widely Available desde setembro de 2024; closedby sem Safari em agosto de 2026 | Botão inerte: script mínimo com showModal e close |
| Popover API | Baseline Newly Available em 27 de janeiro de 2025; Widely Available só em julho de 2027 | Caixa comum em fluxo, visível e utilizável |
| Customizable select | Em desenvolvimento no CSSWG | Select nativo sem enfeite, acessível e com o teclado certo |
| ::scroll-marker no carrossel | Cerca de 69% de cobertura | Faixa rola por toque e encaixe, sem indicadores; Embla com cerca de 4 kB |
| View Transitions same-document | Baseline Newly Available em 14 de outubro de 2025; Firefox 144 fechou o conjunto | Troca de tela sem animação, com o conteúdo inteiro |
Peça o nativo quando a coluna da direita cumpre a função para a fatia do seu público que ainda não tem o recurso.
Fonte: Manual de referência Recursos de frontend moderno, edição setembro de 2026 (Brasil GEO).
Peça os quatro recursos num pedido só, com o plano B de cada um escrito junto, e você evita quatro conversas separadas com o agente. Numa página de reservas isso vira item ativo por :has(), popover ancorado com a diária, carrossel com ::scroll-marker e uma janela modal com botão de fechar à vista.
Antes de aprovar, percorra a tela com Tab, feche com Esc e repita tudo no celular. Depois abra a mesma página num navegador antigo: se o conteúdo continuar visível e utilizável, só sem o enfeite, o plano B está de pé.
Só onde ele não estiver, e a fatia de navegador antigo no painel de visitas justificar, peça uma biblioteca como Floating UI ou Embla.
Guia prático: o que a navegação de 51 templates e 154 componentes esquece
A navegação é a peça que o visitante usa sem pensar, e por isso quase ninguém confere. Medindo o código-fonte dos 51 templates estudados em 07/10/2026:
| Padrão | Templates |
|---|---|
| Cabeçalho com nav ou header | 49 |
| Cabeçalho fixo ou grudado no topo | 34 |
| Botão ou link de destaque dentro do cabeçalho | 32 |
| Links para seções da própria página (âncoras) | 27, com 241 links |
| Links vazios com href="#" | 40, com 353 links |
| Botão de menu para o celular | 26 |
| Links somem no celular sem botão que os substitua | 20, entre eles os 12 premium |
Os dois últimos números juntos dizem muito. Em 20 templates, quem abre no celular perde a navegação inteira e fica só com o botão de contato. Nos 26 que têm botão de menu, só 3 informam se o menu está aberto (aria-expanded), 7 dão nome ao botão e 1 fecha o menu com Esc. Nenhum template marca o link da página atual nem oferece link para pular direto ao conteúdo.
Um erro fica escondido entre os números. Dos 27 templates com âncoras, 19 têm cabeçalho fixo, e só 1 do conjunto reserva espaço para ele com scroll-padding ou scroll-margin. Sem isso, o clique em "Serviços" leva à seção com o título escondido embaixo do cabeçalho.
Os 154 componentes de navegação da biblioteca estudada vão pelo mesmo caminho: 28 menus suspensos, 22 docks, 16 barras laterais e 14 abas, 81 sem campo de licença. Nos 67 exemplos de uso preenchidos, aria-expanded aparece em 1 e o tratamento de Esc em nenhum.
// Teste do Playwright: menu do celular e âncoras que não somem sob o cabeçalho.
import { test, expect } from "@playwright/test";
test.use({ viewport: { width: 390, height: 844 } });
test("navegação no celular", async ({ page }) => {
await page.goto("/");
const botao = page.getByRole("button", { name: /menu/i });
await expect(botao).toHaveAttribute("aria-expanded", "false");
await botao.click();
await expect(botao).toHaveAttribute("aria-expanded", "true");
await page.keyboard.press("Escape");
await expect(botao).toHaveAttribute("aria-expanded", "false");
await expect(botao).toBeFocused();
// Nenhum link vazio na navegação.
expect(await page.locator('nav a[href="#"]').count()).toBe(0);
// A seção de destino aparece abaixo do cabeçalho fixo.
await botao.click();
const link = page.getByRole("navigation").locator('a[href^="#"]').first();
const destino = (await link.getAttribute("href"))!;
await link.click();
await page.waitForTimeout(800);
const topo = await page.locator(destino).evaluate((e) => e.getBoundingClientRect().top);
const fundoDoCabecalho = await page.locator("header").first()
.evaluate((e) => e.getBoundingClientRect().bottom);
expect(topo).toBeGreaterThanOrEqual(fundoDoCabecalho - 1);
});Conferir a navegação que veio pronta
Cinco passos para o cabeçalho de um template ou de um componente de biblioteca.
Passo 1: Abra a página a 390 px e procure a navegação
Se os links sumiram e nada os substitui, peça um botão de menu ou uma barra curta com as três seções mais usadas. Página longa sem navegação no celular obriga a rolar tudo.
Deu certo quando: No celular, toda seção do menu do computador é alcançável em até dois toques.
Erro comum: Aceitar que o celular fique só com o botão de contato porque "ninguém usa o menu".
Passo 2: Teste o botão de menu pelo teclado
Chegue ao botão com Tab, abra com Enter, feche com Esc e confira se o foco volta ao botão. O botão precisa de nome acessível e de aria-expanded que muda com o estado.
Deu certo quando: O teste do bloco acima passa nos três pontos: estado, Esc e foco.
Erro comum: Usar role="menu" numa lista de links do site; navegação simples é botão que mostra e esconde a lista.
Passo 3: Reserve espaço para o cabeçalho fixo
Com cabeçalho fixo e links para seções, ponha scroll-padding-top no html com a altura do cabeçalho. Clique em cada link e veja se o título da seção aparece inteiro.
Deu certo quando: Todo clique em âncora mostra o título da seção logo abaixo do cabeçalho.
Erro comum: Medir no computador e esquecer que o cabeçalho do celular tem outra altura.
Passo 4: Troque todo href="#" por destino real
Varra o projeto por links vazios. Cada um vira endereço real, âncora de seção ou botão, se a ação não leva a lugar nenhum. O que não tem destino sai do menu.
Deu certo quando: A busca por href="#" volta vazia.
Erro comum: Publicar "Blog" e "Carreiras" no menu apontando para lugar nenhum.
Passo 5: Marque onde a pessoa está
Em sites com várias páginas, o link da página atual recebe aria-current="page" e um estilo visível. Acrescente um link para pular ao conteúdo, que aparece quando recebe foco.
Deu certo quando: Quem navega pelo teclado pula o cabeçalho com um Tab e o leitor de tela anuncia a página atual.
Erro comum: Indicar a página atual só pela cor, sem atributo e sem outra marca visual.
No fim você tem: Uma navegação que existe no celular, abre e fecha pelo teclado, leva cada âncora ao lugar certo e não tem nenhum link vazio.
Guia prático: menu, janela e lista de opções nativos, com o que chegou de julho a setembro
A aula montou menu, popover e janela com o que o navegador já traz. Entre julho e setembro de 2026, três peças mudaram: o Chrome 154 parou de fechar popover e janela quando a pessoa rola a tela no toque, o Safari 27 passou a aceitar o select com visual próprio e o posicionamento ancorado ganhou valor inicial comum no Chrome e no Safari. Você sai daqui pedindo a janela que ocupa a tela no celular e vira caixa no computador, com o mesmo código.
Funções, casos de uso e aplicações
Funções
Recursos de menu e janela que mudaram no segundo semestre
| Função ou recurso | O que faz | Quando usar | Desde |
|---|---|---|---|
| focusgroup (Chrome 150) | Dá navegação por setas, uma parada de Tab e memória do último foco a barra de ferramentas, abas e listas, declarado no HTML | Menu de ação e abas; só no Chromium, então mantenha o script de setas como reserva | 30/06/2026 |
| popover="hint" revisado (Chrome 150) | A dica que abre ao passar o mouse não fecha mais o popover automático vizinho | Dica sobre item de menu que já está aberto | 30/06/2026 |
| position-anchor com valor inicial normal | O valor inicial passa de none para normal; o Safari 27 também troca anchor-valid e anchor-visible para o singular | Revisar o CSS de popover ancorado escrito antes de julho | 28/07/2026 (Chrome 151) e 17/09/2026 (Safari 27) |
| select personalizável (appearance: base-select) | Lista de opções com visual da marca, ::picker(select), ::checkmark e selectedcontent, sem reescrever o comportamento | Seletor de turma, plano ou cidade; o Safari 27 juntou-se ao Chrome 135 e o Firefox ainda pede opção ligada | 17/09/2026 |
| Fechamento por toque fora no evento click (Chrome 154) | Rolar a tela no toque e o clique direito deixam de fechar popover e janela | Menu e janela longos no celular, que fechavam quando a pessoa rolava | 22/09/2026 |
| scroll-marker-group com modo tabs (Chrome 154) | Os pontinhos do carrossel nativo viram abas, com ordem de foco e papel de acessibilidade próprios | Carrossel de depoimentos ou de planos que funciona como abas | 22/09/2026 |
Recurso que só o Chromium tem entra atrás de @supports ou de teste de suporte, com a versão sem ele cumprindo a mesma tarefa.
Casos de uso
Coordenadora de captação de um cursinho pré-vestibular em Fortaleza
Educação
Dor
O formulário de matrícula abria numa janela que, no celular, cortava o botão de enviar atrás da barra do navegador e fechava quando o aluno rolava a lista de turmas.
Solução
Pediu a janela com o elemento dialog: tela cheia com altura dinâmica (100dvh) no celular e caixa centralizada a partir de 640 px, com botão de fechar visível. A lista de turmas virou select personalizável, com o select comum como reserva.
Resultado
O que observar: no GA4, a taxa de envio do formulário no celular nas três semanas depois da troca; no teste manual, se a janela continua aberta ao rolar no Chrome atual.
Cenário construído para fins didáticos. Números não correspondem a cliente específico.
Gestor de mídia de uma rede de academias em Porto Alegre
Marketing digital
Dor
O menu de unidades usava uma biblioteca de posicionamento que pesava mais que o resto da página de campanha.
Solução
Trocou por popover nativo ancorado ao botão, revisou o CSS para o novo valor inicial do position-anchor e deixou a caixa abrir no centro onde a âncora não existe.
Resultado
O que observar: o peso do JavaScript da página antes e depois, e o INP do clique no menu no relatório de Core Web Vitals do mês seguinte.
Cenário construído para fins didáticos. Números não correspondem a cliente específico.
Na prática
Neste portal, o quiz rápido do /educacao abre num dialog nativo, em src/components/educacao/quiz-rapido/QuizRapido.tsx. O React só guarda se a janela está aberta; um efeito chama showModal(), que prende o foco e fecha com Esc, e o evento close devolve o estado. No celular, as classes deixam a janela em tela cheia com 100dvh, a altura que acompanha a barra do navegador; a partir de 640 px (sm:), ela vira caixa centralizada com fundo escurecido pelo backdrop. Uma marcação só serve aos dois tamanhos.
useEffect(() => {
const d = dialogo.current;
if (!d) return;
if (aberto && !d.open) d.showModal(); // foco preso e Esc fecha
if (!aberto && d.open) d.close();
}, [aberto]);
<dialog
ref={dialogo}
onClose={() => setAberto(false)}
aria-labelledby="quiz-rapido-titulo"
className="m-0 h-[100dvh] max-h-[100dvh] w-full max-w-full overflow-hidden border-0
bg-[var(--bg)] p-0 text-[var(--text)] backdrop:bg-black/60
sm:m-auto sm:h-auto sm:max-h-[90vh] sm:max-w-2xl sm:rounded-[var(--radius-lg)]
sm:border sm:border-[var(--border)]"
>
{/* conteúdo só monta com a janela aberta */}
</dialog>Uma janela que serve ao celular e ao computador
Passo 1: Use dialog e showModal no lugar de um div com z-index
O navegador prende o foco, fecha com Esc e põe o resto da página fora do alcance do leitor de tela.
Deu certo quando: O Tab não escapa para trás da janela.
Erro comum: Janela feita de div, que deixa o foco passear pela página escondida.
Passo 2: Mande a altura dinâmica no celular
100dvh acompanha a barra do navegador; 100vh esconde o botão de enviar atrás dela.
Deu certo quando: O botão de enviar aparece inteiro a 390 px.
Erro comum: Testar só no simulador do computador, onde a barra não existe.
Passo 3: Troque o layout pelo tamanho e mantenha o mesmo componente
As classes sm: viram a mesma janela numa caixa centralizada, como no quiz do portal.
Deu certo quando: Uma marcação, dois arranjos.
Erro comum: Manter um modal para celular e outro para computador.
Passo 4: Confira o fechamento nos dois navegadores do público
No Chrome 154 a rolagem no toque não fecha mais; em versão anterior e em outros navegadores, teste a rolagem dentro da janela.
Deu certo quando: A janela só fecha quando a pessoa pede.
Erro comum: Prender o fechamento ao toque fora como único caminho.
No fim você tem: Janela nativa, acessível pelo teclado, inteira no celular e com o mesmo código no computador.
Armadilhas
Recurso novo entra com a reserva escrita junto
Todo recurso que falta em um navegador do público chega ao pedido com o que acontece sem ele.
- focusgroup só no Chromium: o script de setas continua
- select personalizável sem Firefox: o select comum é a reserva
- CSS de âncora antigo pode mudar com o novo valor inicial
- Janela sem botão de fechar visível prende quem usa leitor de tela
- Fechar ao tocar fora nunca é o único jeito de sair
O teste: Abra a página num navegador sem o recurso: dá para concluir a tarefa?
alexandrecaramaschi.com/educacao/frontends-com-vibecoding
Aplicar os recursos ao projeto
Se a figura ultrapassar a área visível, deslize para os lados. Pelo teclado, foque a figura e use as setas.
O guia de práticas do WAI mostra como um controle montado à mão avisa o que é, em que estado está e como se opera. Ele entra quando o HTML pronto não dá conta. Colar um papel ARIA nomeia a peça, mas não cria comportamento.
Nas abas do relatório, ligue cada aba ao painel dela e marque qual está escolhida. Faça as setas andarem entre as abas, como o padrão descreve. Trocar de painel na hora só vale se a troca for instantânea; se demorar, exija Enter para confirmar a escolha.
Ande pelas abas com as setas, entre no painel com Tab e ouça o que o leitor de tela fala. Link de menu continua sendo link dentro do bloco de navegação: marcar como menu obriga a implementar outro teclado inteiro. Repita o teste em dois navegadores.
Floating UI calcula onde encaixar uma caixinha que flutua perto do botão, sem cair fora da tela. É o cálculo chato de posição, resolvido. Chame quando a dica, o menu ou o balão precisam se virar em espaço apertado; o comportamento acessível continua com você.
Passe o botão e a caixa de ajuda para computePosition e aplique as coordenadas que voltarem. Acrescente flip, que vira a caixa de lado quando falta espaço, e shift, que a empurra para dentro da tela. Ligue autoUpdate enquanto ela estiver aberta e desligue ao fechar.
Role a página, mude o tamanho da janela e abra a caixa colada na borda direita. Posição certa não dá nome acessível, nem abertura por teclado, nem saída por Escape. Nunca esconda informação necessária numa caixa que só aparece sob o mouse, porque no celular ela some.
Se a figura ultrapassar a área visível, deslize para os lados. Pelo teclado, foque a figura e use as setas.
O elemento dialog do HTML já traz a janela modal de fábrica. Aberta por showModal, ela sobe para a camada de cima e congela o resto da página. Escolha quando a tarefa precisa ser concluída ou cancelada ali; você economiza biblioteca e ganha o comportamento do navegador.
Para editar um endereço, dê ao dialog um título e ligue os dois com aria-labelledby, o atributo que aponta para o nome. Abra pelo botão com showModal, coloque o foco no primeiro campo e mantenha um cancelar. Feche com close ou por um formulário de método dialog.
Aperte Escape, circule com Tab e confira se o foco volta para onde fazia sentido. Só ligar o atributo open abre a caixa sem travar o fundo, e a página atrás segue clicável. Teste texto longo, teclado do celular e zoom antes de salvar dados reais.
Laboratório visual: projetar menus, popovers e modais acessíveis
Entrada, foco, escolha, fechamento e retorno
Escolha um horário
Os horários demonstrativos são 14h e 16h. Nenhuma reserva será enviada.
Selecione cada estado acima. A simulação permanece em “Enviando” para permitir a inspeção; escolha “Confirmado” ou “Erro” para examinar as saídas.
Seu caderno neste capítulo
Abrir o caderno completoSelecione um trecho do capítulo para destacar ou anotar. Nos vídeos e áudios, use Anotar este momento. No teclado, selecione com Shift e as setas e use Alt+Shift+D para destacar ou Alt+Shift+N para anotar.
Salvo neste navegador. Entre na sua conta para levar o caderno a outros aparelhos.
Entre na sua conta para compartilhar o que aprendeu e convidar alguém para estudar com você.
Conexões deste capítulo
Veja o que estudar antes e para onde seguir depois, explore os conceitos e compare abordagens em outros cursos. Sugestões marcadas como “sugerido pelo grafo” vêm dos assuntos em comum, não do índice do curso.
Estude antes
- CursoClaude Code CLI: Programação com IA
- Montar vídeo em React com Remotion e renderizar na sua máquina ou na AWSsugerido pelo grafoApresenta o conceito “React”, usado em “Projetar menus, popovers e modais acessíveis”.
- CursoVídeo com IA: Claude Code, Codex e ElevenLabssugerido pelo grafo20 capítulos de “Frontends com Vibecoding” usam conceitos apresentados em “Vídeo com IA: Claude Code, Codex e ElevenLabs”.
- CursoNode.js: JavaScript no Servidorsugerido pelo grafo20 capítulos de “Frontends com Vibecoding” usam conceitos apresentados em “Node.js: JavaScript no Servidor”.
Depois, siga para
- CursoPrompt Engineering para Executivossugerido pelo grafo7 capítulos de “Prompt Engineering para Executivos” usam conceitos apresentados em “Frontends com Vibecoding”.
- CursoLLM FinOps: o custo da IA para quem assina o orçamentosugerido pelo grafo3 capítulos de “LLM FinOps: o custo da IA para quem assina o orçamento” usam conceitos apresentados em “Frontends com Vibecoding”.
Conceitos deste capítulo
O mesmo assunto em outros cursos
- Software e IA para executivosMedir a velocidade que o cliente sente no celular deleExaminar conexões de Medir a velocidade que o cliente sente no celular dele
- Astra profundoAdaptar a experiência ao espaço disponívelExaminar conexões de Adaptar a experiência ao espaço disponível
- MCP com Chrome: Automação com IAFazer o agente ler a página em texto em vez de fotografarExaminar conexões de Fazer o agente ler a página em texto em vez de fotografar
- Google Ads 2027Preparar a página de destino para o clique e para a expansão de URLExaminar conexões de Preparar a página de destino para o clique e para a expansão de URL
Voltar ao capítulo anterior: Projetar uma linguagem de ícones e ilustração
Leve uma ideia para a prática
Escolha uma atividade no painel e volte a este curso quando quiser.
Atividades e evolução ficam reunidas no painel da sua conta.
Laboratórios ao vivo deste curso
Cada laboratório monta a biblioteca real dentro da aula, com controles para você mexer e o código do exemplo para copiar. O texto de reserva descreve a demonstração para quem lê sem JavaScript. O link abre o capítulo já no ponto do laboratório.
Base UI
Prenda o foco num diálogo sem escrever o comportamento
Abra a janela Agendar visita, percorra os campos com Tab, feche com Esc e acompanhe no painel onde o foco está e para onde ele volta.
Pacote
Abrir no capítuloConstruir componentes, usar o registro e publicar o catálogo@base-ui/reactLucide e Iconify
Mantenha uma família de ícones coerente e nomeie cada botão
Mude tamanho, traço e cor da barra, veja o símbolo do Iconify acompanhar o Lucide e rode a auditoria de nomes acessíveis.
Pacote
Abrir no capítuloProjetar uma linguagem de ícones e ilustraçãolucide-react, @iconify/reactMotion
Testar os três sinais que autorizam o terceiro degrau
Reproduza a entrada em sequência, aperte o botão com mola e arraste o cartão; ajuste rigidez e amortecimento e veja o que muda.
Pacote
Abrir no capítuloProjetar movimento, assinatura e narrativas por rolagemmotionVidstack
Ver o que separa um player de um vídeo cru
Dê o play, troque a velocidade e ligue a legenda; o painel ao lado mostra o estado que o player publica e que a etiqueta crua esconde.
Pacote
Abrir no capítuloProjetar movimento, assinatura e narrativas por rolagem@vidstack/reactcmdk
Achar um destino entre quarenta digitando três letras
Abra a paleta, digite fat e escolha um item só pelo teclado; o painel embaixo conta o que o filtro deixou passar e quanto tempo levou.
Pacote
Abrir no capítuloConstruir formulários e estados de interaçãocmdknumber-flow
Animar indicadores que mudam ao vivo com formato pt-BR
Veja três indicadores fictícios rolarem a cada troca e compare com o número em texto puro. Pause, reinicie e observe o separador de milhar.
Pacote
Abrir no capítuloProjetar gráficos e painéis pela decisão@number-flow/reactApache ECharts
Medir o custo de desenhar 200 mil pontos com o ECharts
Troque o tipo de gráfico e o volume de pontos, leia o tempo de desenho e alterne o eixo em zero. Mude o tema do site e veja as cores refazerem.
Pacote
Abrir no capítuloProjetar gráficos e painéis pela decisãoechartssharp e svgo
Escolher a variante certa da imagem e conferir o teto de 150 KB
Arraste a largura da tela simulada, troque a densidade entre 1x e 2x e veja qual arquivo o navegador escolhe, quanto ele pesa e se cabe no orçamento.
Pacote
Abrir no capítuloValidar desempenho, autenticação e segurançasharp, svgoReact Flow (xyflow)
Disparar um pedido e ver o orquestrador distribuir, conferir e responder
Alterne paralelo e sequencial, ligue e desligue a verificação e use a medição real; observe o tempo de parede em cada combinação.
Pacote
Abrir no capítuloConstruir as cenas do orquestrador, do paralelismo e do robô@xyflow/reactGSAP, Web Workers e number-flow
Dividir mil tarefas entre trabalhadores reais e correr três ritmos de geração
Execute com um trabalhador, depois com quatro e oito, e compare o tempo; em seguida percorra a corrida e separe a espera inicial do ritmo.
Pacote
Abrir no capítuloConstruir as cenas do orquestrador, do paralelismo e do robôgsap, @number-flow/reactThree.js com React Three Fiber, drei, Rapier e XR
Carregar o braço robótico, ver a física trabalhar e entrar na cena com o visor
Carregue a cena pelo botão, inicie o ciclo, mostre os ângulos das juntas, troque a câmera e a velocidade; com visor, entre em realidade virtual.
Pacote
Abrir no capítuloConstruir as cenas do orquestrador, do paralelismo e do robôthree, @react-three/fiber, @react-three/drei, @react-three/rapier, @react-three/xrMotion
Ver quatro agentes em raias trocando mensagens e a revisão devolvendo um trecho
Reproduza os 60 s de modelo, percorra o tempo pelo controle e esconda as mensagens para comparar a leitura; repare no tempo de parede contra a soma.
Pacote
Abrir no capítuloEspecificar, medir e produzir as cenas da máquinamotionMediaRecorder, Canvas 2D e Long Animation Frames (plataforma)
Medir o quadro da cena e gravar seis segundos dela em vídeo
Suba as partículas, force um bloqueio de 300 ms e leia o medidor; depois grave seis segundos, veja o codec escolhido e baixe o arquivo.
Pacote
Abrir no capítuloEspecificar, medir e produzir as cenas da máquinasem pacotesigma.js, graphology com ForceAtlas2 e cosmos.gl
Ver o grafo real do curso e um grafo de milhares de nós se organizarem em aglomerados
Rode o layout no grafo real, passe o mouse pelos nós e busque uma entidade; troque para 20 mil nós e leia o medidor; depois carregue o modo massivo na placa de vídeo.
Pacote
Abrir no capítuloAnimar um grafo de conhecimento amplo em movimentosigma, graphology, graphology-layout-forceatlas2, graphology-generators, @cosmos.gl/graph
Capítulos vizinhos em Frontends com Vibecoding
- 06Construir componentes, usar o registro e publicar o catálogo
- 07Projetar uma linguagem de ícones e ilustração
- 08Projetar menus, popovers e modais acessíveis
- 09Adaptar o layout ao celular e ao contêiner
- 10Projetar movimento, assinatura e narrativas por rolagem