Introduction
The dropdown menu is a type of design pattern by which we can hide or reveal a dropdown box, making it easy for users to navigate websites and applications. When the user clicks or hovers on the menu, then it gracefully displays predefined content or a list of items.
In this tutorial, we will delve into the process of designing engaging dropdown menus that can be smoothly incorporated into a navigation bar or any part of a web development. Initially, we will start with CSS-exclusive methods, such as the :focus and :focus-within pseudo-classes, along with a workaround using checkboxes. Subsequently, we will further expand on this topic by employing JavaScript and tackling the constraints encountered with CSS-only techniques.
We will also explore constructing dropdown menus suitable for various screen sizes, incorporating semantic HTML elements and ARIA attributes to enhance accessibility for screen readers.
Creating a Dropdown Menu with the CSS: focus pseudo-class>
We can initiate the construction of an inclusive dropdown menu by starting with the implementation of semantic and interactive HTML5 components where suitable.
By utilizing the code below, we can create a basic menu button and its corresponding dropdown options:
<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>
Here, the <button> element serves as an interactive component that triggers the dropdown's activation. In order to enhance accessibility, we opt for an unordered list to accommodate the dropdown items. This approach aids screen readers in recognizing the quantity of links available within the dropdown menu.
Let us apply the CSS property.
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.
Let us apply the CSS property.
.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;
}
In the preceding code snippet, the setup has been implemented to place the drop-down menu beneath the navigation button. By using the code shown above, we have successfully applied the position: relative attribute to the parent container and positioned the drop-down itself with the position: absolute property.
Now let's see the complete code:
HTML code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
/* 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%;
visibility: hidden;
overflow: hidden;
}
.dropdown-content li {
background: #2f3238;
border-radius: 0.5em;
position: relative;
left: 100%;
transition: 0.5s;
transition-delay: calc(60ms * var(--delay));
}
.dropdown:focus-within .dropdown-content li {
left: 0;
}
.dropdown:focus-within .dropdown-content {
visibility: visible;
}
.dropdown:focus-within .dropdown-btn > .arrow {
transform: rotate(180deg);
}
.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;
}
/* base styles */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: "Segoe UI", sans-serif;
color: #333;
/* background: #cecece; */
}
</style>
</head>
<body>
<div style="text-align: center; margin-top: 2rem;">
<h2>#2: Using CSS :focus-within</h2>
<p>Click on the button to display the dropdown menu</p>
</div>
<div class="dropdown" tabindex="0">
<button class="dropdown-btn" aria-haspopup="menu">
<span>Framework</span>
<span class="arrow"></span>
</button>
<ul class="dropdown-content" role="menu">
<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>
</body>
</html>
Output:
Toggling the Dropdown using a CSS Checkbox Hack
In the prior code implementation, if the user moved the cursor away from the menu, the choices would disappear. However, by leveraging CSS selectors intelligently, we can utilize the checked state of an HTML checkbox to switch the dropdown display.
Now, in our earlier code, we will substitute 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>
Now, it's time to incorporate some CSS styles into the existing CSS file.
input[type="checkbox"] {
opacity: 0;
position: absolute;
}
input[type="checkbox"]:focus + label {
box-shadow: 0 0 20px rgb(83, 83, 83);
}
Now, it's necessary to adjust the opacity to 0 in order to conceal the input checkbox, rather than using the display: none; attribute. Additionally, it's crucial to ensure that the button gains focus when a user engages with it.
Now, we can substitute either the 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);
}
Now we have to modify 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;
} */
Adding a Transforming Effect
Now, it is necessary to incorporate a transition effect that vertically expands the menu. Therefore, adjustments need to be made to the styles of .dropdown-content both prior to and following the activation of the dropdown.
.dropdown-content {
/* ... */
transform: translateY(-1em);
transition: transform ease 0.3s;
}
input[type="checkbox"]:checked ~ .dropdown-content {
visibility: visible;
transform: translateY(0);
}
Now let's see the complete code:
HTML code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
/* 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%;
visibility: hidden;
/* overflow: hidden; */
transform: translateY(-1em);
transition: transform ease 0.3s;
}
input[type="checkbox"]:checked ~ .dropdown-content {
visibility: visible;
transform: translateY(0);
}
.dropdown-content li {
background: #2f3238;
border-radius: 0.5em;
}
input[type="checkbox"]:checked + label > .arrow {
transform: rotate(180deg);
}
input[type="checkbox"] {
opacity: 0;
position: absolute;
}
input[type="checkbox"]:focus + label {
box-shadow: 0 0 20px rgb(83, 83, 83);
}
.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;
}
/* base styles */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: "Segoe UI", sans-serif;
color: #333;
/* background: #cecece; */
}
</style>
</head>
<body>
<div style="text-align: center; margin-top: 2rem;">
<h2>#3: Toggle with checkbox (Hack)</h2>
<p>Click on the button and toggle the dropdown menu</p>
</div>
<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" role="menu">
<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>
</body>
</html>
Output:
Creating Dropdown Menus with CSS + JavaScript
In the earlier example, we generated the dropdown menu using HTML and CSS exclusively. This time, we will enhance the project by incorporating JavaScript to toggle the dropdown instead of relying on the checkbox workaround. Let's update the existing code accordingly.
/* .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 the provided code snippet, custom class names are employed in place of CSS pseudo-classes. These custom class names are dynamically assigned to the desired elements through JavaScript.
With the utilization of the following JavaScript code snippet, we execute the JavaScript procedure. The procedure commences by targeting the button, caret, and dropdown components. Subsequently, it monitors for a click event on the button and dynamically applies 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 a click event to the dropdown button
dropdownBtn.addEventListener("click", () => {
// add rotate to caret element
dropdownCaret.classList.toggle("arrow-rotate");
//Add open styles to the menu element
dropdownContent.classList.toggle("menu-open");
});
Let's examine the entire code following the inclusion of the JavaScript script:
HTML code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
/* 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%;
visibility: hidden;
overflow: hidden;
}
.dropdown-content li {
background: #2f3238;
border-radius: 0.5em;
position: relative;
left: 100%;
transition: 0.5s;
transition-delay: calc(60ms * var(--delay));
}
.dropdown-content.menu-open li {
left: 0;
}
.dropdown-content.menu-open {
visibility: visible;
}
.arrow.arrow-rotate {
transform: rotate(180deg);
}
.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;
}
/* base styles */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: "Segoe UI", sans-serif;
color: #333;
/* background: #cecece; */
}
</style>
</head>
<body>
<div style="text-align: center; margin-top: 2rem;">
<h2>#4: Toggle with JavaScript</h2>
<p>Click on the button and toggle the dropdown menu</p>
</div>
<div class="dropdown">
<button class="dropdown-btn" aria-label="menu button" aria-haspopup="menu" aria-expanded="false" aria-controls="dropdown-menu">
<span>Framework</span>
<span class="arrow"></span>
</button>
<ul class="dropdown-content" role="menu" id="dropdown-menu">
<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>
<script>
const dropdownBtn = document.querySelector(".dropdown-btn");
const dropdownCaret = document.querySelector(".arrow");
const dropdownContent = document.querySelector(".dropdown-content");
//Add a click event to the dropdown button
dropdownBtn.addEventListener("click", () => {
// add rotate to caret element
dropdownCaret.classList.toggle("arrow-rotate");
//Add open styles to the menu element
dropdownContent.classList.toggle("menu-open");
dropdownBtn.setAttribute(
"aria-expanded",
dropdownBtn.getAttribute("aria-expanded") === "true"? "false": "true"
);
});
</script>
</body>
</html>
Output:
Let's enhance the HTML code above by modifying the ARIA attribute.
<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>
Additionally, we need to modify the aria-expanded attribute according to the present state within the JavaScript file.
dropdownBtn.addEventListener("click", () => {
//...
dropdownBtn.setAttribute(
"aria-expanded",
dropdownBtn.getAttribute("aria-expanded") === "true"? "false": "true"
);
});
Adding a Dropdown Menu to a Navbar
Integrating a dropdown menu into a navigation bar can enhance user experience and functionality. This feature can be achieved by leveraging CSS to style and position the dropdown elements effectively.
By the culmination of the procedure, we will have effectively constructed a CSS dropdown menu that is responsive.
Code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<Style>
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;
visibility: hidden;
transform: translateY(-1em);
transition: transform ease 0.5s;
}
.dropdown {
padding: 2px 1.5rem;
height: 0;
overflow: hidden;
transition: height ease 0.2s;
}
input[type="checkbox"]:checked ~ nav .menus {
visibility: visible;
transform: translateY(0);
}
li:focus-within .dropdown {
height: 135px;
}
li:focus-within .arrow {
transform: rotate(180deg);
}
.menus li {
position: relative;
left: 100%;
transition: 0.2s;
transition-delay: calc(60ms * var(--delay));
}
input[type="checkbox"]:checked ~ nav > ul li {
left: 0;
}
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;
}
/* 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;
}
}
</Style>
</head>
<body>
<header>
<div class="header-content">
<a href="#" class="logo">C# Tutorial</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>
</body>
</html>
Output:
Navbar and Dropdown Menu Markup
In the following code snippet, a fundamental layout has been established, featuring three main menu options: Home, Services, and About. Within the Services option, there is an expandable dropdown functionality showcasing three inner 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>
Output:
It will give us the below output:
Mobile-first Dropdown Menu Design with CSS
Now, let's generate a navigation bar for mobile devices. To accomplish this, we need to incorporate certain CSS code into our stylesheet.
Css code:
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;
}
Output:
If we adjust the screen resolution to match that of a mobile phone, the resulting display will appear as follows:
Interacting with the Dropdown Blocks
Now it is necessary to conceal the primary dropdown button using the visibility property and adjust the height to 0. Therefore, we need to make modifications in our CSS file:
.menus {
/* ... */
visibility: hidden;
transform: translateY(-1em);
transition: transform ease 0.5s;
}
.dropdown {
/* ... */
height: 0;
overflow: hidden;
transition: height ease 0.2s;
}
Now, it is necessary to employ the checked CSS pseudo-selector along with the focus-within pseudo-class in order to switch the main dropdown and trigger the Services dropdown. As a result, the CSS file will be updated as follows:
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
For improved visual appeal, incorporating animation can enhance the user experience by utilizing the following tools:
.menus li {
position: relative;
left: 100%;
transition: 0.2s;
transition-delay: calc(60ms * var(--delay));
}
input[type="checkbox"]:checked ~ nav > ul li {
left: 0;
}
Designing Dropdown Menus for Larger Screens with CSS
Now, it is essential to incorporate CSS media queries and establish the styling regulations for a viewport width of 640px and higher:
/* 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;
}
}