Como criar um componente de navegação lateral

Uma visão geral básica de como criar uma navegação lateral responsiva com deslizamentos

Nesta postagem, quero compartilhar como criei o protótipo de um componente Sidenav para a Web que é responsivo, com estado, oferece suporte à navegação por teclado, funciona com e sem JavaScript e funciona em vários navegadores. Teste a demonstração.

Se preferir vídeo, aqui está uma versão do YouTube desta postagem:

Visão geral

Criar um sistema de navegação responsivo é uma tarefa difícil. Alguns usuários usam um teclado, outros têm computadores potentes e outros usam um dispositivo móvel pequeno. Todos que visitarem poderão abrir e fechar o menu.

Demonstração do layout responsivo de computador para dispositivos móveis
Tema claro e escuro no iOS e no Android

Táticas para Web

Nesta exploração de componentes, tive o prazer de combinar alguns recursos críticos da plataforma da web:

  1. CSS :target
  2. Grade CSS
  3. transforms de CSS
  4. Consultas de mídia CSS para janela de visualização e preferência do usuário
  5. JS para focus melhorias de UX

Minha solução tem uma barra lateral e alterna apenas quando em uma janela de visualização "para dispositivos móveis" de 540px ou menos. O 540px vai ser nosso ponto de interrupção para alternar entre o layout interativo para dispositivos móveis e o layout estático para computadores.

Pseudoclasse :target CSS

Um link <a> define o hash do URL como #sidenav-open e o outro como vazio (''). Por fim, um elemento tem o id para corresponder ao hash:

<a href="#sidenav-open" id="sidenav-button" title="Open Menu" aria-label="Open Menu">

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>

<aside id="sidenav-open">
  …
</aside>

Clicar em cada um desses links muda o estado de hash do URL da nossa página. Em seguida, com uma pseudoclasse, mostro e oculto a navegação lateral:

@media (max-width: 540px) {
  #sidenav-open {
    visibility: hidden;
  }

  #sidenav-open:target {
    visibility: visible;
  }
}

Grade CSS

Antes, eu só usei layouts e componentes de navegação lateral de posição absoluta ou fixa. No entanto, a grade, com a sintaxe grid-area, permite atribuir vários elementos à mesma linha ou coluna.

Pilhas

O elemento de layout principal #sidenav-container é uma grade que cria uma linha e duas colunas, uma de cada uma delas chamada stack. Quando o espaço é restrito, o CSS atribui todos os filhos do elemento <main> ao mesmo nome de grade, colocando todos os elementos no mesmo espaço, criando uma pilha.

#sidenav-container {
  display: grid;
  grid: [stack] 1fr / min-content [stack] 1fr;
  min-height: 100vh;
}

@media (max-width: 540px) {
  #sidenav-container > * {
    grid-area: stack;
  }
}

O <aside> é o elemento de animação que contém a navegação lateral. Ele tem dois filhos: o contêiner de navegação <nav>, chamado [nav], e um pano de fundo <a> chamado [escape], que é usado para fechar o menu.

#sidenav-open {
  display: grid;
  grid-template-columns: [nav] 2fr [escape] 1fr;
}

Ajuste 2fr e 1fr para encontrar a proporção desejada para a sobreposição do menu e o botão "Fechar" de espaço negativo.

Uma demonstração do que acontece quando você muda a proporção.

Transformações e transições CSS em 3D

Agora, nosso layout é empilhado no tamanho de janela de visualização para dispositivos móveis. Enquanto eu não adicionar alguns estilos, ele vai se sobrepor ao artigo por padrão. Aqui está a UX que estou buscando na próxima seção:

  • Animar abertura e fechamento
  • Use animações com movimento apenas se o usuário aceitar.
  • Animar visibility para que o foco do teclado não entre no elemento fora da tela

À medida que começo a implementar animações em movimento, quero começar pensando na acessibilidade.

Movimento acessível

Nem todo mundo vai querer uma experiência com movimento de deslizar. Na nossa solução, essa preferência é aplicada ajustando uma variável CSS --duration dentro de uma consulta de mídia. Esse valor de consulta de mídia representa a preferência do sistema operacional de um usuário por movimento (se disponível).

#sidenav-open {
  --duration: .6s;
}

@media (prefers-reduced-motion: reduce) {
  #sidenav-open {
    --duration: 1ms;
  }
}
Uma demonstração da interação com e sem duração aplicada.

Agora, quando a navegação lateral desliza aberta e fechada, se um usuário prefere movimento reduzido, movo o elemento instantaneamente para a visualização, mantendo o estado sem movimento.

Transição, transformação e tradução

Navegação lateral de saída (padrão)

Para definir o estado padrão da navegação lateral em dispositivos móveis como um estado fora da tela, posio o elemento com transform: translateX(-110vw).

Observe que adicionei outro 10vw ao código típico fora da tela de -100vw para garantir que a box-shadow da navegação lateral não apareça na janela de visualização principal quando ela estiver oculta.

@media (max-width: 540px) {
  #sidenav-open {
    visibility: hidden;
    transform: translateX(-110vw);
    will-change: transform;
    transition:
      transform var(--duration) var(--easeOutExpo),
      visibility 0s linear var(--duration);
  }
}
Navegação lateral em

Quando o elemento #sidenav corresponder a :target, defina a posição translateX() como 0 da base inicial e observe enquanto o CSS desliza o elemento da posição de fora de -110vw para a posição "in" de 0 em var(--duration) quando o hash do URL é modificado.

@media (max-width: 540px) {
  #sidenav-open:target {
    visibility: visible;
    transform: translateX(0);
    transition:
      transform var(--duration) var(--easeOutExpo);
  }
}

Visibilidade da transição

O objetivo agora é ocultar o menu dos leitores de tela quando ele estiver fora da tela, para que os sistemas não coloquem o foco em um menu fora da tela. Para isso, defina uma transição de visibilidade quando o :target for alterado.

  • Ao entrar, não faça a transição da visibilidade. Fique visível imediatamente para que eu possa ver o elemento deslizando e aceitar o foco.
  • Ao sair, a visibilidade da transição, mas a atrasará para hidden no final da transição.

Melhorias na UX de acessibilidade

Essa solução depende da alteração do URL para que o estado seja gerenciado. Naturalmente, o elemento <a> precisa ser usado aqui e recebe alguns recursos de acessibilidade bem sem custo financeiro. Vamos adornar nossos elementos interativos com rótulos que expressam claramente a intenção.

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu"></a>

<a href="#sidenav-open" id="sidenav-button" class="hamburger" title="Open Menu" aria-label="Open Menu">
  <svg>...</svg>
</a>
Uma demonstração da UX de narração e interação com o teclado.

Agora, nossos botões de interação principais indicam claramente a intenção deles para mouse e teclado.

:is(:hover, :focus)

Esse pseudosseletor funcional do CSS nos permite ser rapidamente inclusivos com nossos estilos de passar o cursor, compartilhando-os em foco.

.hamburger:is(:hover, :focus) svg > line {
  stroke: hsl(var(--brandHSL));
}

Espalhe no JavaScript

Pressione escape para fechar

A tecla Escape no teclado vai fechar o menu à direita? Vamos fazer isso.

const sidenav = document.querySelector('#sidenav-open');

sidenav.addEventListener('keyup', event => {
  if (event.code === 'Escape') document.location.hash = '';
});
Histórico do navegador

Para evitar que a interação de abrir e fechar empilhe várias entradas no histórico do navegador, adicione o seguinte JavaScript inline ao botão "Fechar":

<a href="#" id="sidenav-close" title="Close Menu" aria-label="Close Menu" onchange="history.go(-1)"></a>

Isso removerá a entrada do histórico de URLs no fechamento, fazendo com que o menu nunca tivesse sido aberto.

UX de foco

O próximo snippet nos ajuda a focar nos botões de abrir e fechar depois que eles abrem ou fecham. Quero facilitar essa alternância.

sidenav.addEventListener('transitionend', e => {
  const isOpen = document.location.hash === '#sidenav-open';

  isOpen
      ? document.querySelector('#sidenav-close').focus()
      : document.querySelector('#sidenav-button').focus();
})

Quando a navegação lateral abrir, foque o botão "Fechar". Quando a navegação lateral fechar, foque no botão “Abrir”. Para fazer isso, chame focus() no elemento em JavaScript.

Conclusão

Agora que você sabe como eu fiz isso, como você iria?! Isso resulta em uma arquitetura de componentes divertida. Quem vai fazer a primeira versão com slots? 🙂

Vamos diversificar nossas abordagens e aprender todas as maneiras de criar na Web. Crie um Glitch, envie um tweet para mim sua versão e vou adicioná-la à seção Remixes da comunidade abaixo.

Remixes da comunidade