[CSS] Menu Dropdown usando apenas CSS.
Introdução
Hoje venho apresentar um projeto de um menu dropdown (menu que aparece logo abaixo quando um botão é apertado) com apenas CSS na sua construção, usando pseudo-classes no lugar da famosa classe "active".
O objetivo desse post é desmistificar para programadores Front-End iniciantes (ou não) que muitas vezes tendem a pensar que a solução de certo problema específico é encontrado apenas no script sendo que, muitas vezes a solução já existe no CSS (até com menos dor de cabeça).
O projeto no Codepen pode ser encontrado neste link caso queira apenas passar o olho no código e ver o projeto funcionando, abaixo estará a explicação do projeto. Não aconselho a leitura para pessoas mais experientes em CSS pois a explicação será extensa e ira conter conceitos bem básicos como seletores, pseudo-classes e métodos .
Sumário
Importante freezar que este projeto só é possível graças ao seletor de irmão adjacente, caracterizado pelo sinal de mais (+).
div + a {
background: red;
}
Este seletor permite selecionar todo elemento que for seguido do elemento passado inicialmente no seletor. No exemplo acima, estamos definindo um fundo vermelho para todo elemento <a>
que estiver logo após uma <div>
, está ordem sera definida pelo código encontrado no documento HTML.
<div></div>
<a class="home">Home</a>
<section></section>
<a class="about">About</a>
<a>
que contém a classe home será selecionado, já o que possúi a classe about não.
O projeto também utiliza pseudo-classes como :active
, que permite aplicar estilos a um elemento no momento em que o mesmo está sendo pressionado, outra pseudo-classe utilizada é o :hover
que seleciona o elemento no momento em que o ponteiro paira por cima dele.
A syntáxe de uma pseudo-classe é como no exemplo abaixo.
.Menu:active {
opacity: 50%;
}
Menu tera sua opacidade reduzida na metade quando o mesmo for pressionado.
O código HTML
O projeto se inicia apenas com uma <div>
que tem papel de segurar todo nosso menu no código, essa <div>
recebe a classe de menu.
<div class="menu"></div>
Dentro do nosso menu teremos dois elementos essenciais que seram eles um <button>
, o qual acionara o nosso menu, e um elemento especial que fara sentido mais pra frente na explicação mas no momento, sera apenas uma <div>
qualquer que receberá de início seu display como none
.
<button>
ira receber a classe open-menu e a nossa <div>
ira receber menu-trigger.
<div class="menu">
<button class="open-menu" type="button">
Menu
</button>
<div class="menu-trigger"></div>
</div>
Note que menu-trigger está logo após open-menu no código.
E por último mas não menos importante, teremos a navegação do menu que receberá a classe nav-menu, e seus items com a classe nav-menu-item.
<div class="menu">
<button class="open-menu" type="button">
Menu
</button>
<div class="menu-trigger"></div>
<ul class="nav-menu">
<li class="nav-menu-item">Edit</li>
<li class="nav-menu-item">Duplicate</li>
<li class="nav-menu-item">Archive</li>
<li class="nav-menu-item">Move</li>
<li class="nav-menu-item">Share</li>
<li class="nav-menu-item">Add to favorites</li>
<li class="nav-menu-item">Delete</li>
</ul>
</div>
nav-menu também receberá seu display como
none
para ser ativado posteriormente.
O que temos até agora considerando nossas propiedades de estilo já foram aplicadas no CSS é o seguinte.
Apenas nosso
<button>
open-menu é possível de ser visualizado de inicio.
O código CSS
Para melhor entendimento e também não deixar a explicação mais extensa, não irei citar aqui as propiedades referentes ao estilo do nosso menu, apenas aquelas que forem essenciais para o funcionamento do mesmo.
Nossa <div>
menu irá receber de importante apenas uma mudança no seu fluxo de posicionamento que passará a ser relative
e não mais static
, isso nos ajudara a posicionar outros elementos no futuro.
.menu {
position: relative;
}
Nossa navegação terá de essencial apenas os seguintes estilos:
.nav-menu {
opacity: 0;
position: absolute;
pointer-events: none;
}
Esses estilos iram garantir primeiro que nossa navegação não seja visível inicialmente e que não possa ser alvo de eventos de ponteiro (para que o usuário não consiga clicar nos seus items sem a navegação está ativada) e também tera seu fluxo de posicionamento alterado com seu position definido como absolute
para garantir que seu conteúdo sobreponha tudo e que seu tamanho não influêncie no posicionamento dos demais elementos na tela.
Agora aqui é onde a mágica começa a acontecer pois, teremos no nosso <button>
open-menu, propiedades no seu estado :active
que influenciaram no comportamento daquela nossa <div>
menu-trigger especial usando o seletor de irmao adjacente do CSS ( +
).
.open-menu:active + .menu-trigger {
display: block;
}
No exemplo acima estamos dizendo que, quando nosso elemento <button>
open-menu estiver sendo pressionado, queremos que o próximo elemento a ele receba a propiedade de display definida como block
.
Como dito mais acima, sabemos que o próximo elemento a ele é a nossa <div>
menu-trigger especial que até então estava com seu display definido como none
.
Os estilos essenciais contidos nessa <div>
são o seguinte.
.menu-trigger {
width: 100%;
height: 100%;
opacity: 0;
display: none;
position: absolute;
top: 0;
}
Esses estilos garantem que <div>
menu-trigger se comporte da seguinte maneira quando for ativado.
No momento em que <button>
open-menu é pressionado, menu-trigger passa a poder ser visualizado e interagido pelo usuário.
No exemplo acima, menu-trigger pode ser visualizado como um retangulo azul que cobre todo nosso
<button>
porém, no projeto final iremos utilizar apenas sua habilidade de interação com o usuário, seu estilo final será invisivel (opacity: 0%;
) independente se estiver com displaynone
oublock
.
Agora, menu-trigger se mantém ativo enquanto o ponteiro estiver pairando por cima dele, mas como isso é possível?
Simples, temos na nossa <div>
menu-trigger os seguintes estilos quando o ponteiro está em cima da mesma:
.menu-trigger:hover {
display: block;
}
Então o que acontece aqui é o seguinte, quando o usuário pressiona o <button>
open-menu, nossa <div>
menu-trigger aparece e sobrepõe o <button>
e, já que o ponteiro já estava acima dele, automaticamente os estilos contidos no estado de :hover
de menu-trigger é ativado o que faz com que o mesmo se mantenha com display block
enquanto o mouse pairar sobre ele.
Lembrando que no projeto em sí a
<div>
menu-trigger possui sua opacidade zerada porém, sua interação é preservada.
E para completar, o mesmo fluxo de eventos que acontecem com o <button>
open-menu e a <div>
menu-trigger, acontece com a <ul>
nav-menu e menu-trigger.
Quando a <div>
menu-trigger é pairada pelo ponteiro, além de aplicar os estilos a ela mesma de display block
, também ativa o seguintes código:
.menu-trigger:hover + .nav-menu {
pointer-events: auto;
opacity: 100%;
}
Que diz que, quando menu-trigger estiver no estado de :hover
, então o próximo elemento a ele (que no caso será o nosso menu) terá seus eventos de ponteiro restabelecidos e sua opacidade será de cem porcento.
Mas para que restabelecemos os eventos de ponteiro da nossa navegação?
Simples, na nossa navegação temos estilos específicos que só seram aplicados quando nosso menu estiver no estado de :hover
, que no final irá usar a mesma lógica que foi usada para ativar a <div>
menu-trigger e manter a mesma ativada.
.nav-menu:hover {
pointer-events: initial;
opacity: 1;
}
Os eventos de ponteiro sendo restaurados só nesse momento, garante que a navegação só será visível depois que o fluxo de eventos do <button>
open-menu e da <div>
menu-trigger acontecer e o usuário manter o mouse pairando acima ou da navegação em sí, ou acima de menu-trigger.
Toda essa estrutura resulta no seguinte comportamento:
Conclusão
Esse projeto não tem como fim o uso do mesmo em projetos reais, o objetivo aqui é apenas mostrar que não existe solução única para um problema e que não existe ferramente única para resolução do mesmo.
A vontade de fazer esse projeto vem de uma frustação antiga minha de quando eu achava que tudo que era relacionado com interação só era possível usando JavaScript quando na verdade não é bem assim. Existem interações simples que podem sim ser feitas com CSS em projetos reais sem prejudicar a experiência do usuário (UX).
Enfim, obrigado quem chegou até aqui e espero ter contribúido para sua evolução seja lá de qual for a maneira, futuramente irei trazer mais desse tipo de conteúdo, um abraço.
Acredito que a grande questão aqui é a acessibilidade. Eu também já tentei fazer menus dropdown com CSS, mas a acessibilidade sempre fica comprometida.
É sempre bom a gente testar a navegação por teclado, dentre outras coisas, quando vamos criar um componente.
Depois que eu vi esse vídeo aqui, fiquei impressionado com a quantidade de detalhes que precisam ser considerados ao criar um dropdown. https://www.youtube.com/watch?v=pcMYcjtWwVI
Muito bom! Uma dúvida que fiquei por que você quis usar o nav
em vez de ul
com li
? Fico pensando que talvez não seja muito apropriado usar tag semantica de navegação pra dropdown... Geralmente dropdown é pra ações.
Parabéns!
Simples e funcional.
Fico pensando até que ponto precisamos utilizar determinados frameworks se com o avanço que o HTML e CSS tiveram com poucos códigos desenvolvemos "componentes nativamente", digo no sentido de utilizar o mínimo de código necessário, limpo e organizado.
Eu adoro nos meus projetos trabalhar com CSS ou SASS por que gosto de fazer as coisas do meu jeito, a utilização das bibliotecas como tailwind ou bootstrap te limita aquilo a qual ela é destinada, post muito bom!!
Muito bem explicado, gostei demais. Se der faz mais posts assim, talvez algum projeto ao algo do tipo.
Ótimo tutorial, eu sempre tento usar o menos possivel de JavaScript, pra ter um código mais rápido e limpo.