How to Build a CSS Dropdown Menu

The following snippet demonstrates how to build a dropdown menu with CSS (no JavaScript) that maintains its state when clicked. The special sauce in this demo is the :focus-within pseudo selector, which allows the dropdown to stay open when its child links are clicked.

Step 1: Start with a Navigation Bar

Start with some navbar markup.

file_type_html
<nav class="navbar">
      <ul class="navbar-nav">
        <li class="nav-item">Home</li>
        <li class="nav-item">About</li>

        <!-- Dropdown will go here -->

        <li class="nav-item">Login</li>
      </ul>
    </nav>

The navbar is just simple flex container.

file_type_css
.navbar {
    height: 70px;
    width: 100%;
    background: black;
    color: white;
}

.navbar-nav {
    list-style-type: none;
    margin: 0;
    padding: 0;

    display: flex;
    align-items: center;
    justify-content: space-evenly;
    height: 100%;
}

Step 2: Add a Dropdown Menu

Add a list item to the navbar that has a dropdown nested below it.

file_type_html
        <!-- Dropdown will go here -->

        <li class="nav-item has-dropdown">

          <a href="#">Theme</a>

          <ul class="dropdown">

            <li class="dropdown-item">
              <a href="#">light</a>
            </li>
            <li class="dropdown-item">
              <a href="#">dark</a>
            </li>
            <li class="dropdown-item">
              <a href="#">solarize</a>
            </li>

          </ul>
        </li>

The dropdown starts with an opacity of zero to keep it hidden until it becomes focused. It is also a flex container to control the layout direction of its children.

file_type_css
.dropdown {
    opacity: 0;
    position: absolute;
    width: 500px;
    z-index: 2;
    background: black;
    
    display: flex;
    align-items: center;
    justify-content: space-around;
    height: 3rem;
    margin-top: 2rem;
    padding: 0.5rem;
   
    transform: translateX(-40%);
    transition: opacity .15s ease-out;
}

.dropdown-item a {
    width: 100%;
    height: 100%;
}

Step 3: Open the Dropdown with focus-within

When any of the <a> elements in the dropdown are focused (by being clicked), the opacity is updated to 1. The focus-within selector stays active while ANY of the children inside the dropdown have focus - that’s much easier than listening to click events with JavaScript. The transition in the previous step gives it a subtle fade-in animation.

file_type_css
.has-dropdown:focus-within .dropdown   {
    opacity: 1;
    pointer-events: auto;
}

Questions?

Ask questions via GitHub below OR chat on Slack #questions