How to Create an Impressive Dropdown Menu with CSS

In this CSS tutorial, you will learn how to create responsive, accessible dropdown menus with CSS for both keyboard and mouse users. Dropdown menus are a common design pattern in modern apps. 

Dropdown menus are a type of design pattern that allows us to hide or reveal a dropdown box, making it easy for users to navigate websites and applications. With a simple click or hover, this type of menu will gracefully display predefined content or a list of items.

In this guide, we’ll explore the art of creating captivating dropdown menus that can seamlessly integrate into a navigation bar or any section of a web project. We’ll begin with CSS-only solutions, the :focus and :focus-within pseudo-classes and a checkbox hack. Then, we’ll delve into the realm of JavaScript, addressing the limitations faced with CSS-only approaches.

We’ll discuss building dropdown menus for both larger and smaller screens, as well as using semantic HTML elements and ARIA attributes to assist screen readers. By the end of this tutorial, you’ll be able to create amazing, responsive, accessible dropdown menus for both keyboard and mouse users.

Let’s get started!

Contents:

  • Creating a dropdown menu with the CSS :focus pseudo-class
    • Interacting with the dropdown
    • Adding a sliding effect transition to the dropdown
    • Specifying a delay for dropdown items
  • Creating a dropdown menu with the CSS :focus-within pseudo-class
    • Accessibility support for the Safari browser
  • Toggling the dropdown using a CSS checkbox hack
    • Adding a transform effect
  • Creating a dropdown menu with CSS + JavaScript
  • Enhancing dropdown menus for screen readers
  • Adding a dropdown menu to a Navbar
    • Navbar and dropdown menu markup
  • Mobile-first dropdown menu design with CSS
    • What is happening in the CSS?
    • Interacting with the dropdown blocks
    • Adding a sliding effect transition to dropdown items
  • Designing dropdown menus for larger screens with CSS

Creating a dropdown menu with the CSS :focus pseudo-class

To start, let’s use the CSS :focus selector to create a dropdown menu that looks like this:

Building an accessible dropdown menu begins with using semantic and interactive HTML5 elements where appropriate.

The following code snippet establishes a simple menu button along with its associated dropdown items:

<div class="dropdown">
  <button class="dropdown-btn">
    <span>Framework</span>
    <span class="arrow"></span>
  </button>
  <ul class="dropdown-content">
    <li><a href="#">React</a></li>
    <li><a href="#">Angular</a></li>
    <li><a href="#">Vue</a></li>
    <li><a href="#">Svelte</a></li>
  </ul>
</div>

The <button> element is an interactive component that we’ll use to initiate the opening of the dropdown. To ensure accessibility, we use an unordered list for the dropdown items; this enables screen readers to identify the number of links within the dropdown.

Now, let’s apply the following base CSS styling to the markup:

/* Dropdown styles */

.dropdown {
  max-width: 13em;
  margin: 80px auto 0;
  position: relative;
  width: 100%;
}
.dropdown-btn {
  background: #1d1f24;
  font-size: 18px;
  width: 100%;
  border: none;
  color: #fff;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.7em 0.5em;
  border-radius: 0.5em;
  cursor: pointer;
}
.arrow {
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 6px solid #fff;
  transition: transform ease-in-out 0.3s;
}
.dropdown-content {
  list-style: none;
  position: absolute;
  top: 3.2em;
  width: 100%;
}
.dropdown-content li {
  background: #2f3238;
  border-radius: 0.5em;
}
.dropdown-content li:hover {
  background: #1d1f24;
}
.dropdown-content li a {
  display: block;
  padding: 0.7em 0.5em;
  color: #fff;
  margin: 0.1em 0;
  text-decoration: none;
}

The styling rules are configured to position the dropdown box below the menu button. This is achieved by applying a position: relative property to the container element and a position: absolute to the actual dropdown.

We also implement various other CSS styles to enhance the overall aesthetics of the dropdown. The final appearance should resemble the following:

Final Appearance CSS Dropdown Menu

Interacting with the dropdown

Next, let’s hide the dropdown until the menu button receives a focus due to the user’s action — either by a click, a tap, or a press of the keyboard’s tab key.

Let’s update the styles with the following:

.dropdown-content {
  /* ... */
  visibility: hidden;
}
.dropdown-btn:focus + .dropdown-content {
  visibility: visible;
} 
.dropdown-btn:focus > .arrow {
  transform: rotate(180deg);
}

Here we’ve added a style rule that causes the dropdown indicator to rotate when the button is clicked.

Adding a sliding effect transition to the dropdown

Now, let’s add a smooth sliding effect when opening or closing the dropdown menu. This is achieved by applying the following rules:

.dropdown-content {
  /* ... */
  overflow: hidden;
} 
.dropdown-content li {
  /* ... */
  position: relative;
  left: 100%;
  transition: 0.5s;
}
.dropdown-btn:focus + .dropdown-content li {
  left: 0;
}

Applying a position: relative; and a left property to the dropdown items causes them to shift toward the right from their default position. Subsequently, when the button receives focus, they’ll smoothly transition back to their original position.

Specifying a delay for dropdown items

To enhance the visual flow of the dropdown menu, let’s add a delay when the menu items slide in and out.

To implement this, we’ll update the menu items to include a style attribute with a custom property:

<div class="dropdown">
  <!-- ... -->
  <ul class="dropdown-content">
    <li style="--delay: 1;"><a href="#">React</a></li>
    <li style="--delay: 2;"><a href="#">Angular</a></li>
    <li style="--delay: 3;"><a href="#">Vue</a></li>
    <li style="--delay: 4;"><a href="#">Svelte</a></li>
  </ul>
</div>

In the CSS stylesheet, we can access the CSS variables and apply a delay using the transition-delay property, like so:

.dropdown-content li {
  /* ... */
  transition-delay: calc(60ms * var(--delay));
}

Creating a dropdown menu with the CSS :focus-within pseudo-class

If we carefully observe the dropdown implementation with the :focus pseudo-class, we notice that it is only applied to the focused element — in this case, the menu button. Whenever the focus shifts to any other element, including the dropdown items, the dropdown closes.

To provide a more user-friendly experience, we‘d like the dropdown to remain open as long as we click within the active menu area. This is where the :focus-within pseudo-class comes into play.

By applying :focus-within to an element, the associated style rules will apply when any of its child elements, or the element itself, receives focus.

In this case, we’ll apply the :focus-within to the .dropdown container class. This way, when child elements such as the button or the dropdown items receive focus, the corresponding rules will be applied.

Let’s replace :focus with :focus-within in our CSS, resulting in the following:

/* .dropdown-btn:focus + .dropdown-content li {
  left: 0;
} */
.dropdown:focus-within .dropdown-content li {
  left: 0;
}
/* .dropdown-btn:focus + .dropdown-content {
  visibility: visible;
} */
.dropdown:focus-within .dropdown-content {
  visibility: visible;
}
/* .dropdown-btn:focus > .arrow {
  transform: rotate(180deg);
} */
.dropdown:focus-within .dropdown-btn > .arrow {
  transform: rotate(180deg);
}

If we test the dropdown, it should function correctly.

Accessibility support for the Safari browser

By default, using the tab key for navigation is disabled in Safari. We can enable it by opening our Safari Preferences by opening the Advanced settings and checking the “Press Tab to highlight each item on a webpage” checkbox in the Accessibility section.

Sometimes, clicking on the menu button may not result in the dropdown displaying in Safari. An element on Safari will get a :focus-within if it has a tabindex="0". To address the issue, let’s add a tabindex to the element on which we applied the pseudo-class:

<div class="dropdown" tabindex="0">
  <button class="dropdown-btn">
    <!-- ... -->
  </button>
  <ul class="dropdown-content">
    <!-- ... -->
  </ul>
</div>

The dropdown should now work as expected:

Toggling the dropdown using a CSS checkbox hack

With the previous methods, the dropdown could only be closed when the button or dropdown items lost focus. However, by utilizing CSS selectors cleverly, we can take advantage of the checked state of an HTML checkbox to toggle the dropdown.

Based on our HTML structure, let’s replace the button element with an input checkbox:

<div class="dropdown">
  <input type="checkbox" id="dropdown" />
  <label for="dropdown" class="dropdown-btn">
    <span>Framework</span>
    <span class="arrow"></span>
  </label>

  <ul class="dropdown-content">
    <li><a href="#">React</a></li>
    <li><a href="#">Angular</a></li>
    <li><a href="#">Vue</a></li>
    <li><a href="#">Svelte</a></li>
  </ul>
</div>

Then, in addition to the base styles, let’s add the following CSS:

input[type="checkbox"] {
  opacity: 0;
  position: absolute;
}
input[type="checkbox"]:focus + label {
  box-shadow: 0 0 20px rgb(83, 83, 83);
}

Here, we use opacity: 0; to hide the input checkbox instead of a display: none; property and also add styles to ensure that the button receives a focus when a user interacts with it.

Now, let’s replace the style rules for :focus or :focus-within with the :checked CSS pseudo-selector:

/* .dropdown-btn:focus + .dropdown-content {
  visibility: visible;
} */
input[type="checkbox"]:checked ~ .dropdown-content {
  visibility: visible;
}
/* .dropdown-btn:focus > .arrow {
  transform: rotate(180deg);
} */
input[type="checkbox"]:checked + label > .arrow {
  transform: rotate(180deg);
}

After that, we’ll modify the style rules on the li element and remove the sliding effect transition:

.dropdown-content li {
  background: #2f3238;
  border-radius: 0.5em;
/*   position: relative;
  left: 100%;
  transition: 0.5s;
  transition-delay: calc(60ms * var(--delay)); */
}

/* .dropdown-btn:focus + .dropdown-content li {
  left: 0;
} */

Now let’s implement another transition effect.

Adding a transform effect

Let’s add a smooth transition effect that repositions the dropdown vertically. We’ll update the styles for .dropdown-content before and after the dropdown is active:

.dropdown-content {
  /* ... */
  transform: translateY(-1em);
  transition: transform ease 0.3s;
}
input[type="checkbox"]:checked ~ .dropdown-content {
  visibility: visible;
  transform: translateY(0);
}

The dropdown should now behave as expected:

Creating dropdown menus with CSS + JavaScript

Up until now, we’ve used CSS only to create dropdown menus. Now, let’s use JavaScript to toggle the dropdown instead of using the checkbox hack. We’ll begin by making modifications to the CSS:

/* .dropdown-btn:focus + .dropdown-content li {
  left: 0;
} */
.dropdown-content.menu-open li {
  left: 0;
} 
/* .dropdown-btn:focus + .dropdown-content {
  visibility: visible;
} */
.dropdown-content.menu-open {
  visibility: visible;
} 
/* .dropdown-btn:focus > .arrow {
  transform: rotate(180deg);
} */
.arrow.arrow-rotate {
  transform: rotate(180deg);
}

In this approach, we move away from using the CSS pseudo-classes and instead employ custom class names that we will add to the target elements using JavaScript.

The JavaScript code snippet below demonstrates the process. It begins by selecting the button, caret, and dropdown elements. Then, it listens for a click event on the button and dynamically adds classes to the caret and dropdown elements:

const dropdownBtn = document.querySelector(".dropdown-btn");
const dropdownCaret = document.querySelector(".arrow");
const dropdownContent = document.querySelector(".dropdown-content");

// add click event to dropdown button
dropdownBtn.addEventListener("click", () => {
  // add rotate to caret element
  dropdownCaret.classList.toggle("arrow-rotate");
  // add open styles to menu element
  dropdownContent.classList.toggle("menu-open");
});

Let’s test the dropdown and ensure it works! See the project on CodePen:

Enhancing dropdown menus for screen readers

To improve accessibility for screen reader users, let’s add ARIA attributes that effectively convey the behavior, functionality, and state of the dropdown. Here are the ARIA attributes we will implement:

  • aria-label: Add this attribute to interactive elements, like the menu button, to provide a clear description of its purpose; this attribute allows screen reader software to announce the control correctly
  • aria-haspopup and role: Apply these attributes to the menu button and dropdown, respectively, to inform the screen readers that there is a popup. Both must match the type of dropdown – in our case, menu
  • aria-controls: Maps the controlling element to the expanded widget; we’ll set the id of the expanded widget to the value of the aria-controls attribute on the controlling element
  • aria-expanded: Indicates whether the dropdown is currently hidden or visible; this attribute’s value should toggle between true and false based on the state of the dropdown. We’ll dynamically update the value using JavaScript to reflect the current state of the dropdown

Let’s update the HTML markup to include the ARIA attributes:

<div class="dropdown">
  <button
    class="dropdown-btn"
    aria-label="menu button"
    aria-haspopup="menu"
    aria-expanded="false"
    aria-controls="dropdown-menu"
  >
    <!-- ... -->
  </button>
  <ul class="dropdown-content" role="menu" id="dropdown-menu">
    <!-- ... -->
  </ul>
</div>

Then, with JavaScript, let’s dynamically update the aria-expanded attribute based on the current state:

dropdownBtn.addEventListener("click", () => {
  //...
  dropdownBtn.setAttribute(
    "aria-expanded",
    dropdownBtn.getAttribute("aria-expanded") === "true" ? "false" : "true"
  );
});

If we toggle the button, the attribute should also update:

Attribute Updates Toggle Button ARIA

Adding a dropdown menu to a Navbar

Utilizing the knowledge we have acquired thus far, let’s integrate a dropdown menu within a navigation bar using CSS.

By the end of the process, we’ll have successfully developed a responsive CSS dropdown menu:

We employed a CSS-only solution by using the input checkbox to toggle the main dropdown on smaller screens and the :focus-within pseudo-class to activate the Services dropdown.

Navbar and dropdown menu markup

The below code establishes a basic navigation structure comprising three primary menu items: Home, Services, and About. The Services item includes a dropdown feature with three nested ul submenu items:

<header>
  <div class="header-content">

    <a href="#" class="logo">Logo</a>

    <input type="checkbox" id="hamburger">
    <label for="hamburger" class="hamburger">
      <span></span>
    </label>

    <nav>
      <ul class="menus">
        <li style="--delay: 1;"><a href="#">Home</a></li>
        <li style="--delay: 2;" tabindex="0">
          <button>
            <span>Services</span>
            <span class="arrow"></span>
          </button>
          <ul class="dropdown">
            <li><a href="#">Web design</a></li>
            <li><a href="#">Web dev</a></li>
            <li><a href="#">Web</a></li>
          </ul>
        </li>
        <li style="--delay: 3;"><a href="#">About</a></li>
      </ul>
    </nav>
  </div>
</header> 

The output should look like this:

Navbar Dropdown Menu Markup

Mobile-first dropdown menu design with CSS

We’ll start by styling the navigation for smaller screens. In the CSS file, we’ll add the following styles:

header {
  position: relative;
  background: #1d1f24;
  color: #fff;
}

.header-content {
  align-items: center;
  max-width: 1200px;
  margin: 0 auto;
  padding: 10px 20px;
}

.logo {
  text-decoration: none;
  font-size: 25px;
  color: inherit;
  margin-right: 20px;
}

input[type="checkbox"] {
  opacity: 0;
  position: absolute;
  right: 0;
}

.hamburger {
  padding: 23px 20px;
  position: absolute;
  cursor: pointer;
  right: 0;
  top: 0;
}

.hamburger span {
  width: 20px;
  height: 3px;
  display: block;
  background: #fff;
  position: relative;
}

.hamburger span::before,
.hamburger span::after  {
  content: "";
  position: absolute;
  display: block;
  background: inherit;
  width: inherit;
  height: inherit;
}

.hamburger span::before {
  top: 8px;
}

.hamburger span::after {
  bottom: 8px;
}

input[type="checkbox"]:focus + .hamburger {
  box-shadow: 0 0 20px rgba(0,0,0,.45);
}

ul {
  list-style: none;
}

ul li {
  font-size: 18px;
}

ul li a {
   display: block;
   text-decoration: none;
}

ul li a, ul li button {
  padding: 0.7rem 1rem;
  text-align: left;
  color: inherit;
}

.menus {
  position: absolute;
  top: 3.2rem;
  left: 0;
  right: 0;
  background: #2f3238;
}

.dropdown {
  padding: 2px 1.5rem;
}

button {
  font-size: inherit;
  border: none;
  background-color: transparent;
  cursor: pointer;
  width: 100%;
  display: flex;
  align-items: center;
  gap: 1em;
}

.arrow { 
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 6px solid #fff;
  transition: transform ease-in-out 0.3s;
}

/* base styles */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
body {
  font-family: "Segoe UI", sans-serif;
  color: #333;
}

Now we should have a dropdown menu that looks like this:

Dropdown Menu Optimized Mobile

What is happening in the CSS?

As expected, using the CSS position: absolute; on the dropdown element, places it relative to the navigation bar, as seen in the image above.

We then used the ::before and ::after pseudo-classes to transform the input checkbox into a custom hamburger menu to activate a dropdown on smaller screens. Subsequently, several other CSS styles were applied to elevate the overall visual appeal.

Interacting with the dropdown blocks

To hide the main dropdown, we’ll use the CSS visibility property and specify height: 0 for the Services dropdown:

.menus {
  /* ... */

  visibility: hidden;
  transform: translateY(-1em);
  transition: transform ease 0.5s;
}

.dropdown {
  /* ... */

  height: 0;
  overflow: hidden;
  transition: height ease 0.2s;
}

Here, we’ve included additional CSS properties to ensure a seamless transition effect.

Next, we’ll use the :checked CSS pseudo-selector and the :focus-within pseudo-class to toggle the main dropdown and activate the Services dropdown, respectively:

input[type="checkbox"]:checked ~ nav .menus {
  visibility: visible;
  transform: translateY(0);
}

li:focus-within .dropdown {
  height: 135px;
}

li:focus-within .arrow {
  transform: rotate(180deg);
} 

Adding a sliding effect transition to dropdown items

Similar to our previous approach, let’s add animation using the following style rules:

.menus li {
  position: relative;
  left: 100%;
  transition: 0.2s;
  transition-delay: calc(60ms * var(--delay));
}

input[type="checkbox"]:checked ~ nav > ul li {
  left: 0;
}

The dropdown should now behave like so:

Dropdown Menu Slide Effect Transition

Designing dropdown menus for larger screens with CSS

We’ll use the CSS media queries and define the style rules for a screen width of 640px and above:

/* MEDIA QUERIES  */
@media (min-width: 640px) {

  .header-content {
    display: flex;
  }

  .menus {
    position: static; 
    visibility: visible;
    background: transparent;
    display: flex;
    transform: initial;
  }

  .menus li {
    font-size: 14px;
    left: auto;
  }

  .hamburger, input[type="checkbox"] {
    display: none;
  }

  ul li a:hover,
  ul li button:hover {
    background-color: #2f3238;
  }

  .dropdown {
    position: absolute;
    top: 48px;
    right: 0;
    left: auto;
    z-index: 99;
    min-width: 10rem;
    padding: 0;
    background-color: #1d1f24;
    border-radius: 0 0 0.5rem 0.5rem;
  }

}

In this CSS code, we added a display: flex; to the header content to position the logo and navigation menus side-by-side. Then, we added a position: static; declaration on the navigation content to override the absolute positioning we used for mobile design and place the nav items in the normal flow of the page.

Conclusion

In this article, we’ve demonstrated how to create an impressive dropdown menu using CSS. Our sample menu is designed to be accessible to users who rely on keyboards, mouse, and screen readers. Additionally, we explored how to enhance the dropdown functionality using JavaScript.

Dropdown menus are a common design pattern in modern apps. 

If you have questions or contributions, please share your thoughts in the comment section. Finally, endeavor to share this lesson around the web.

Source: https://blog.logrocket.com

#css 

How to Create an Impressive Dropdown Menu with CSS
1.65 GEEK