CSS — Dropdowns
Three ways to build a dropdown
| Approach | Trigger | Best for |
|---|---|---|
:hover + :focus-within | pointer + keyboard | desktop nav menus |
<details>/<summary> | native toggle | zero-JS accordions/menus |
| JS class toggling | full control | production apps |
Pattern 1 — pure CSS
<div class="dropdown">
<button class="dropdown-btn">Courses ▾</button>
<ul class="dropdown-menu">
<li><a href="/html">HTML</a></li>
<li><a href="/css">CSS</a></li>
<li><a href="/js">JavaScript</a></li>
</ul>
</div>
.dropdown {
position: relative; /* anchor */
display: inline-block;
}
.dropdown-btn {
padding: 10px 16px;
background: none;
border: none;
font: inherit;
cursor: pointer;
}
.dropdown-menu {
display: none;
position: absolute;
top: 100%; /* hang below the button */
left: 0;
min-width: 180px;
margin: 0;
padding: 6px;
list-style: none;
background: white;
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0,0,0,.15);
}
.dropdown-menu a {
display: block;
padding: 8px 12px;
border-radius: 6px;
text-decoration: none;
color: #334155;
}
.dropdown-menu a:hover { background: #f0fdfa; }
/* reveal on hover AND keyboard focus */
.dropdown:hover .dropdown-menu,
.dropdown:focus-within .dropdown-menu {
display: block;
}
(Hovering or tabbing into the wrapper reveals the floating menu.)
:focus-within is the accessibility hero — it stays matched while any descendant has focus, so tabbing through links keeps the menu open.
Gap problem — hover bridges
A gap between button and menu makes hover flicker. Bridge invisibly:
.dropdown-menu {
margin-top: 8px;
}
.dropdown::after { /* invisible hover bridge */
content: "";
position: absolute;
top: 100%; left: 0; right: 0;
height: 8px;
}
Pattern 2 — <details> (zero CSS logic)
<details class="menu-dd">
<summary>Courses</summary>
<ul>
<li><a href="/html">HTML</a></li>
<li><a href="/css">CSS</a></li>
</ul>
</details>
Native open/close semantics, works without JavaScript. Restyle summary's marker:
.menu-dd summary { cursor: pointer; list-style: none; }
.menu-dd[open] summary { color: teal; }
Pattern 3 — JS class toggle (most control)
btn.addEventListener("click", () => {
const open = menu.classList.toggle("open");
btn.setAttribute("aria-expanded", open);
});
document.addEventListener("click", e => {
if (!wrapper.contains(e.target)) close(); // outside-click closes
});
document.addEventListener("keydown", e => {
if (e.key === "Escape") close();
});
.dropdown-menu {
opacity: 0;
transform: translateY(-4px);
pointer-events: none;
transition: opacity .2s, transform .2s;
}
.dropdown-menu.open {
opacity: 1;
transform: none;
pointer-events: auto;
}
Opacity+transform animate smoothly (unlike display), while pointer-events gates interaction — the modern show/hide recipe.
Accessibility essentials
aria-expandedon the trigger reflects statearia-haspopup="true"when it opens a menu- Escape closes; focus returns to trigger
- Outside-click closes (document listener)
Mini Practice
- Pure-CSS dropdown with focus-within support.
- Add the hover-bridge; kill the flicker.
- Rebuild with
<details>in five minutes. - JS version with animation, aria-expanded, Escape + outside-click.
- Multi-level: nested dropdown inside a dropdown.
Next: pagination →
Related Topics
Frequently Asked Questions about Dropdowns
What is Dropdowns in CSS?
Dropdowns is a fundamental concept in CSS. This lesson explains it step by step with clear examples, making it easy for beginners to understand.
How do I learn Dropdowns?
Start by reading the explanation above, then try the code examples. Practice by modifying the examples and experimenting with different values. Hands-on practice is the best way to learn Dropdowns.
Why is Dropdowns important in CSS?
Dropdowns is essential for CSS development. Understanding this concept will help you write better code and solve real-world problems more effectively.