How to Create a Dropdown Menu in CSS
This recipe builds a click-toggle dropdown menu with a minimal, accessible JavaScript pattern, and shows the CSS-only hover version for comparison.
Two ways to build a dropdown
A dropdown can be built two common ways. A CSS-only version shows the menu on :hover, which needs no script at all but works poorly on touch devices, since there is no hover state to trigger it, and it is awkward for keyboard users. A JavaScript click-toggle version works identically on touch, mouse, and keyboard, and can be closed with Escape or an outside click. This recipe builds the click-toggle version as the main technique, then shows the hover version for context.
The click-toggle dropdown
The toggle is a real button with aria-haspopup and aria-expanded, and the menu is an absolutely positioned list tucked just under it. A click handler flips an open class with classList.toggle; a second listener closes the menu if a click lands outside both the button and the menu; a third listens for the Escape key so the menu can always be dismissed without a mouse.
Example
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: sans-serif; margin: 40px; }
.dropdown { position: relative; display: inline-block; }
.dropdown-toggle {
padding: 10px 16px;
background: #2563eb;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.dropdown-menu {
display: none;
position: absolute;
top: 100%;
left: 0;
margin-top: 4px;
background: white;
border: 1px solid #e5e7eb;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0,0,0,0.12);
list-style: none;
padding: 4px 0;
min-width: 160px;
z-index: 20;
}
.dropdown-menu.open { display: block; }
.dropdown-menu li a {
display: block;
padding: 8px 16px;
color: #111827;
text-decoration: none;
}
.dropdown-menu li a:hover { background: #f3f4f6; }
</style>
</head>
<body>
<div class='dropdown'>
<button class='dropdown-toggle' id='ddToggle' aria-haspopup='true' aria-expanded='false'>
Options ▾
</button>
<ul class='dropdown-menu' id='ddMenu'>
<li><a href='#'>Edit</a></li>
<li><a href='#'>Duplicate</a></li>
<li><a href='#'>Delete</a></li>
</ul>
</div>
<script>
const toggle = document.getElementById('ddToggle');
const menu = document.getElementById('ddMenu');
toggle.addEventListener('click', () => {
const isOpen = menu.classList.toggle('open');
toggle.setAttribute('aria-expanded', isOpen);
});
document.addEventListener('click', (e) => {
if (!toggle.contains(e.target) && !menu.contains(e.target)) {
menu.classList.remove('open');
toggle.setAttribute('aria-expanded', 'false');
}
});
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
menu.classList.remove('open');
toggle.setAttribute('aria-expanded', 'false');
}
});
</script>
</body>
</html>The CSS-only hover version
For contrast, the simplest possible dropdown needs no JavaScript: wrap the button and menu in a position: relative container, hide the menu by default, and reveal it with :hover or :focus-within on the wrapper. :focus-within matters here - it keeps the menu open while a keyboard user has tabbed into it, not just while the mouse happens to be hovering.
Example
<!DOCTYPE html>
<html>
<head>
<style>
.dropdown-hover { position: relative; display: inline-block; font-family: sans-serif; }
.dropdown-hover .dropdown-menu {
display: none;
position: absolute;
top: 100%;
left: 0;
background: white;
border: 1px solid #e5e7eb;
list-style: none;
padding: 4px 0;
min-width: 140px;
}
.dropdown-hover:hover .dropdown-menu,
.dropdown-hover:focus-within .dropdown-menu {
display: block;
}
</style>
</head>
<body>
<div class='dropdown-hover'>
<button>Options</button>
<ul class='dropdown-menu'>
<li><a href='#'>Edit</a></li>
<li><a href='#'>Delete</a></li>
</ul>
</div>
</body>
</html>Positioning the menu
Either version relies on the same positioning trick: the wrapper needs position: relative so it becomes the reference point, and the menu needs position: absolute with top: 100% to sit directly below it and left: 0 to align its left edge. A z-index on the menu keeps it layered above whatever content happens to sit underneath.
Exercise: Navigation
What CSS technique makes a navbar stick to the top of the viewport after scrolling, without JavaScript?
Frequently Asked Questions
- How do I create a dropdown menu with only HTML and CSS?
- Wrap the trigger and the menu in a container with
position: relative, hide the menu withdisplay: none, and show it on:hoveror:focus-within. Position the menu absolutely so it floats over the page instead of shifting the layout. - How do I make a dropdown accessible with a keyboard?
- Use
:focus-withinalongside:hoverso tabbing into the menu opens it, and make the trigger a real button. Hover alone locks out keyboard and touch users, since neither fires a hover state reliably.