Frontends com Vibecoding · Capítulo 10 de 35 · 33 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.
Você sai sabendo testar sozinho, só com o teclado, se o menu, a janela sobreposta e o aviso flutuante do site funcionam para quem não usa mouse. Tempo de leitura: cerca de 33 minutos, e o teste no site da empresa, mais dez.
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.
Ver como texto
Comparação entre rótulos genéricos e rótulos que dizem o destino. Continuar vira Criar conta grátis, Enviar vira Baixar o PDF e Saiba mais vira Ver planos e preços. O rótulo genérico obriga a adivinhar o que acontece depois do clique; o rótulo com destino entrega a pista antes do clique, o chamado aroma de informação.
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.
| 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.
Ver como texto
Anatomia de um endereço que guarda o estado da tela, num cenário ilustrativo de loja de sofás. Depois do caminho loja.com.br/sofas vêm três pedaços: aba igual a tecido, que guarda a aba aberta; cor igual a cinza, que guarda o filtro aplicado; e página igual a dois, que guarda o número da página. Com o estado no endereço, o link compartilhado abre igual e o botão de voltar e o recarregamento mantêm a escolha. Com o estado só na memória da tela, o link chega vazio e o recarregamento apaga a escolha.
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.
Ver como texto
Dropdown com role menu: botão Conta com aria-expanded e anel de foco que navega item a item pelas setas do teclado
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.
Ver como texto
Ciclo de vida de um menu ou gaveta operado só pelo teclado, em cinco passos ligados por setas. Um: abrir com Enter ou toque. Dois: o foco entra e fica preso no painel. Três: escolher com as setas, Home e End. Quatro: fechar com Esc ou com um toque fora. Cinco: o foco volta ao botão de origem, e o ciclo pode recomeçar. Em nenhum passo o foco some da tela.
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.
Ver como texto
Grade com as quatro peças de interface que o navegador já traz prontas. Menu suspenso: a caixa fica presa ao botão que abriu, pelo anchor positioning. Popover: fecha sozinho ao clicar fora, pelo atributo popovertarget. Janela modal: abre e fecha sem script, pelo dialog com closedby. Carrossel: desenha os pontinhos que mostram a posição, pelo pseudoelemento scroll-marker. Em cada cartão, a linha de baixo diz o que se pede no código.
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.
: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.
Ver como texto
Duas faixas de carrossel com três cartões cada. Na primeira, o pseudoelemento scroll-marker desenha três pontinhos de posição abaixo da faixa, o primeiro preenchido. Na segunda, num navegador sem suporte, a faixa rola por toque e encaixe e não mostra os pontinhos. Embaixo, duas barras de peso: o Embla, com cerca de 4 kB, é uma barra curta, e o Swiper, que pesa dez vezes isso, é uma barra dez vezes maior e só se justifica pelo efeito 3D pronto.
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.
| 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
| 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 do exemplo
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 do exemplo
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
O navegador já entrega menu e janela prontos
A janela sobreposta (o elemento dialog) e o conteúdo flutuante (a Popover API) funcionam em todos os navegadores principais, segundo o selo Baseline do MDN. Isso significa menos código para manter e teclado funcionando de fábrica. Biblioteca entra quando o caso pede mais, como o filtro dentro de menu que o Base UI lançou em 09/10/2026.

Ver como texto
Página do MDN Web Docs sobre o elemento HTML dialog, com o selo Baseline Widely available e os ícones dos navegadores, o texto que o define como caixa de diálogo modal ou não modal, um aviso para não usar o atributo tabindex nele e o atributo closedby, que define como a janela pode ser fechada.

Ver como texto
Página do MDN Web Docs sobre a Popover API, com o selo Baseline 2025 Newly available e os ícones dos navegadores, o texto que a apresenta como mecanismo padrão para mostrar conteúdo por cima do resto da página, controlado por atributos HTML ou por JavaScript, e a seção Concepts and usage, que distingue popover modal e não modal.

Ver como texto
Página Menu da documentação do Base UI com a descrição A list of actions in a dropdown, enhanced with keyboard navigation, uma demonstração com o botão Song, o código de exemplo e a seção Anatomy, que começa por Menu.FilterProvider, Menu.Root e Menu.Trigger; à esquerda, a lista de componentes como Dialog, Drawer, Menu e Menubar.
| Tecla | O que deve acontecer | Defeito comum |
|---|---|---|
| Tab | O foco anda pelos itens na ordem de leitura, com contorno visível | Foco invisível ou pulando para o rodapé |
| Enter ou espaço | Abre o menu ou aciona o item focado | Só o clique do mouse funciona |
| Esc | Fecha o menu ou a janela e devolve o foco ao botão que abriu | A janela fecha e o foco some |
| Setas | Movem entre os itens de um menu aberto | Setas rolam a página por trás |
Faça agora: navegue pelo site sem mouse
Passo 1: Abra o site no computador e deixe o mouse de lado.
Deu certo quando: Só o teclado está em uso.
Passo 2: Aperte Tab até chegar ao menu principal e abra-o com Enter.
Deu certo quando: Você vê onde está o foco a cada Tab.
Erro comum: Clicar uma vez "só para começar" e perder a ordem real do foco.
Passo 3: Abra uma janela sobreposta, como um formulário ou um aviso de cookies, e feche com Esc.
Deu certo quando: A janela fecha e o foco volta ao botão que a abriu.
Passo 4: Anote cada falha numa linha, com a página e a tecla.
Deu certo quando: Uma lista curta que o fornecedor consegue reproduzir.
No fim você tem: Um relatório de acessibilidade por teclado, em uma página, pronto para virar pedido de correção.
Menu e janela aprovados pelo teclado
Conferido no site
0/4Aplicar os recursos ao projeto
Ver como texto
Abrir e fechar preserva o percurso
Um acionador abre um painel com controle de fechamento. Uma seta entra no painel e outra retorna ao acionador, representando o retorno de foco após fechar.
Ver como texto
Dois painéis com a mesma janela de editar endereço. À esquerda, aberta por showModal: a janela sobe para a camada de cima, o fundo escurece e congela, e o primeiro campo aparece com o contorno de foco. À direita, aberta só pelo atributo open: a caixa aparece, mas o fundo não escurece e o cursor ainda clica num botão da página de trás.
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: do zero à prática
- Mobile e performance: INP e Core Web Vitalssugerido pelo grafoApresenta o conceito “INP”, usado em “Projetar menus, popovers e modais acessíveis”.
- CursoSíntese e didática com IAsugerido pelo grafoDepois de escolher a informação essencial e desenhar hierarquia, gráficos e fluxos compreensíveis, o aluno pode aplicar esses critérios às interfaces do curso de frontend. A ponte conecta clareza da mensagem, acessibilidade e decisões de apresentação.
- CursoNode.js: JavaScript no Servidorsugerido pelo grafo9 capítulos de “Frontends com Vibecoding” usam conceitos apresentados em “Node.js: JavaScript no Servidor”.
Conceitos deste capítulo
O mesmo assunto em outros cursos
- Astra profundoAdaptar a página ao celular sem perder a ação principalExaminar conexões de Adaptar a página ao celular sem perder a ação principal
- CRO: Otimização de Conversão e ExperimentaçãoLanding pages: clareza acima da dobraExaminar conexões de Landing pages: clareza acima da dobra
- MCP e Chrome: agentes de IA no navegadorQuando ler a estrutura da página e quando usar imagemExaminar conexões de Quando ler a estrutura da página e quando usar imagem
- Motor de crescimento para IAO que corrigir primeiro na página do anúncioExaminar conexões de O que corrigir primeiro na página do anúncio
Terminou a leitura?
Você está aqui: capítulo 10 de 35
0 de 35 concluídos
- Próximo · Capítulo 11 de 35 · 29 minAdaptar o layout ao celular e ao contêiner
- Anterior · Capítulo 9 de 35 · 33 minProjetar uma linguagem de ícones e ilustraçãoVoltar para o capítulo 9
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ãoechartsApache ECharts
Painel grande com visões coordenadas
Clique num canal para filtrar todas as visões, troque a densidade entre 6, 9 e 12 visões, alterne tema e movimento e abra a tela cheia.
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
- 08Construir componentes, usar o registro e publicar o catálogo
- 09Projetar uma linguagem de ícones e ilustração
- 10Projetar menus, popovers e modais acessíveis
- 11Adaptar o layout ao celular e ao contêiner
- 12Projetar movimento, assinatura e narrativas por rolagem