← Biblioteca componentes/navegacao/drawer-fullscreen/ficha.md CódigoTela cheia
navegacao/drawer-fullscreen

Menu mobile de tela cheia

🟢 estavel complexidade baixa prefixo drawer- menu mobile drawer overlay acessibilidade

Incluir · as linhas que se colam num site novo, depois do core (CLAUDE.md §10)

<link rel="stylesheet" href="componentes/navegacao/drawer-fullscreen/estilo.css">
<script src="componentes/navegacao/drawer-fullscreen/script.js"></script>

markup.html · 28 linhas · 1,3 KB

<!-- ==========================================================================
     DRAWER — menu mobile de tela cheia
     Prefixo: drawer-
     Requer: core/tokens.css, core/base.css + este estilo.css e script.js
     Abre por qualquer elemento com [data-drawer-open] na página — em geral
     o hambúrguer do navegacao/nav-fixa-vidro.
     ========================================================================== -->

<div class="drawer sec-dark" data-drawer hidden>
  <div class="wrap drawer-top">
    <a href="#topo" class="drawer-logo" aria-label="Início">
      <img src="../../../showroom/demo/marca.svg" alt="Nome da marca">
    </a>
    <button class="drawer-close" data-drawer-close aria-label="Fechar menu">
      <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4"><path d="M6 6l12 12M18 6L6 18"/></svg>
    </button>
  </div>

  <nav class="drawer-nav" aria-label="Menu">
    <a href="#manifesto" class="drawer-link">Quem somos</a>
    <a href="#solucoes" class="drawer-link">Soluções</a>
    <a href="#estrutura" class="drawer-link">Estrutura</a>
    <a href="#metodo" class="drawer-link">Método</a>
    <a href="#duvidas" class="drawer-link">Dúvidas</a>
  </nav>

  <a href="#contato" class="btn btn-primary drawer-cta">Agende seu diagnóstico</a>
</div>

estilo.css · 43 linhas · 1,7 KB

/* ==========================================================================
   DRAWER — menu mobile de tela cheia
   Prefixo: drawer-
   Tokens usados: --fg --rule --dark --display --pad --nav-h --ease --z-overlay

   Escopo de tom no próprio elemento: o drawer vive fora de qualquer
   <section>, então não herda --fg de ninguém.
   ========================================================================== */

.drawer{
  position:fixed;inset:0;z-index:var(--z-overlay);
  background:var(--dark);
  display:flex;flex-direction:column;
  padding:26px var(--pad);
  transform:translateX(100%);
  transition:transform .5s var(--ease);
}
/* [hidden] é removido pelo script antes de animar. Ele existe para o estado
   inicial: sem ele, leitor de tela e Tab alcançam os links do menu fechado. */
.drawer[hidden]{display:none}
.drawer.is-open{transform:none}

/* padding-inline:0 porque o .wrap aqui está DENTRO de um bloco que já
   aplicou --pad. É a única exceção à regra do grid em toda a biblioteca,
   e existe para o topo do drawer alinhar com o topo da nav. */
.drawer-top{
  display:flex;align-items:center;justify-content:space-between;
  margin-bottom:56px;min-height:var(--nav-h);padding-inline:0;
}
.drawer-logo{line-height:0;display:block}
.drawer-logo img{height:22px;width:auto;display:block}
.drawer-close{background:none;border:none;color:var(--fg);padding:6px;line-height:0}

.drawer-nav{display:flex;flex-direction:column}
.drawer-link{
  font-family:var(--display);font-weight:700;font-size:22px;letter-spacing:-.02em;
  padding:16px 0;border-bottom:1px solid var(--rule);color:var(--fg);
}
.drawer-cta{margin-top:34px;align-self:flex-start}

@media(prefers-reduced-motion:reduce){
  .drawer{transition:none}
}

script.js · 64 linhas · 2,8 KB

/* ==========================================================================
   DRAWER — menu mobile de tela cheia
   Abre por [data-drawer-open], fecha por [data-drawer-close], por clique em
   qualquer link de dentro, e por Esc.
   Trava a rolagem da página pelo core enquanto aberto e devolve o foco a
   quem abriu.
   ========================================================================== */

(function () {
  var drawer = document.querySelector('[data-drawer]');
  if (!drawer) return;

  var opener = null;

  /* Trava e soltura têm de vir em par: o contador do core não perdoa uma
     sobrando. E aqui isso não é teoria — este drawer não prende o foco
     (gotcha da ficha), então o Tab alcança o gatilho atrás do painel aberto
     e um segundo open() deixaria a página travada para sempre. */
  var travado = false;

  function open(trigger) {
    opener = trigger || null;
    drawer.hidden = false;
    /* Um frame entre remover o [hidden] e aplicar .is-open: sem isso o
       elemento nasce já na posição final e o translate não anima. */
    /* O rAF aqui não anima nada: ele só separa o display:block do
       is-open em dois frames, senão o browser agrupa os dois e a
       transição do CSS não chega a existir. Nada a fazer sob
       movimento reduzido — quem desliga a transição é o motion.css. */
    requestAnimationFrame(function () { drawer.classList.add('is-open'); });
    /* Trava do core: contador compartilhado com as outras sobreposições, e
       devolve o overflow anterior em vez de apagá-lo. O guard existe porque
       o menu é a peça mais colada sozinha num bloco de HTML de CMS, onde às
       vezes só entram tokens.css e base.css — sem o motion.js a página atrás
       rola, o que é ruim, mas não é um erro no console. */
    if (!travado && window.LIB && LIB.travarRolagem) { travado = true; LIB.travarRolagem(); }
    var close = drawer.querySelector('[data-drawer-close]');
    if (close) close.focus();
  }

  function close() {
    drawer.classList.remove('is-open');
    if (travado) { travado = false; LIB.soltarRolagem(); }
    if (opener) { opener.focus(); opener = null; }
    /* Só volta a [hidden] depois da transição, senão o painel some de
       imediato em vez de deslizar para fora. */
    setTimeout(function () {
      if (!drawer.classList.contains('is-open')) drawer.hidden = true;
    }, 500);
  }

  document.querySelectorAll('[data-drawer-open]').forEach(function (btn) {
    btn.addEventListener('click', function () { open(btn); });
  });
  drawer.querySelectorAll('[data-drawer-close]').forEach(function (btn) {
    btn.addEventListener('click', close);
  });
  drawer.querySelectorAll('a').forEach(function (a) {
    a.addEventListener('click', close);
  });
  addEventListener('keydown', function (e) {
    if (e.key === 'Escape' && drawer.classList.contains('is-open')) close();
  });
})();

Painel opaco que cobre a tela inteira deslizando da direita. Marca e botão de fechar no topo, alinhados com a altura do menu fixo; links grandes em display, empilhados com divisória; CTA no pé. Trava o scroll da página enquanto está aberto e devolve o foco ao botão que o abriu.


Quando usar

Quando não usar


Padrão editorial

Slot Regra
Links Os mesmos do menu de desktop, na mesma ordem. Divergir entre desktop e mobile é erro, não variação.
CTA O mesmo do desktop, aqui em botão cheio em vez de pílula pequena.

Como usar

<link rel="stylesheet" href="componentes/navegacao/drawer-fullscreen/estilo.css">
<script src="core/motion.js"></script>
<script src="componentes/navegacao/drawer-fullscreen/script.js"></script>

Cole logo depois do menu fixo. Qualquer elemento da página com [data-drawer-open] passa a abri-lo — não é preciso ligar nada.

O core/motion.js entra por causa da trava de rolagem (LIB.travarRolagem()). Sem ele o menu continua abrindo, fechando e devolvendo o foco; só a página atrás passa a rolar por baixo do painel. Está protegido por guard, então a ausência não vira erro no console — mas é degradação de verdade, não detalhe.

Ganchos

Gancho Papel
[data-drawer] raiz do painel
[data-drawer-open] qualquer gatilho na página
[data-drawer-close] botão de fechar dentro do painel
.drawer.is-open estado aberto

Variações


Gotchas


Histórico

Projeto Onde Data Diferenças
Liberta Wealth index.html e quem-somos.html, #drawer 2026-08 Origem. Classes .dl e .logo sem prefixo, .open em vez de .is-open, ids #burger/#drawerClose conhecidos pelo script, sem hidden (o menu fechado era tabulável), sem devolução de foco, borda #1e1e1e crua.

Normalizações aplicadas na extração: prefixo em tudo; .open → .is-open; ids → [data-drawer-open] / [data-drawer-close], o que desacopla o gatilho do painel; adicionado hidden no estado fechado e devolução de foco ao gatilho; borda crua → --rule; escopo de tom explícito no elemento; adicionado prefers-reduced-motion.