Home / Snippets / UI Components /
Dropdown menu
Hover and keyboard-accessible dropdown with a smooth reveal.
Quick implementation
.dropdown {
position: relative;
}
.dropdown-list {
position: absolute;
top: calc(100% + 0.4rem);
left: 0;
min-width: 12rem;
background: oklch(0.2 0.03 260);
border: 1px solid oklch(0.28 0.02 260);
border-radius: 0.5rem;
padding: 0.35rem;
opacity: 0;
visibility: hidden;
transform: translateY(-0.5rem);
transition: opacity 0.15s ease,
transform 0.15s ease,
visibility 0.15s;
box-shadow: 0 8px 24px oklch(0 0 0 / 0.4);
}
.dropdown:hover .dropdown-list,
.dropdown:focus-within .dropdown-list {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.dropdown-list a {
display: block;
padding: 0.5rem 0.75rem;
border-radius: 0.3rem;
color: oklch(0.93 0.01 260);
text-decoration: none;
}
.dropdown-list a:hover,
.dropdown-list a:focus {
background: oklch(0.28 0.05 265);
}Prompt this to your LLM
Includes role, constraints, and edge cases to handle.
You are a senior frontend engineer building a navigation component.
Goal: A dropdown menu that opens on hover and is keyboard accessible via :focus-within — no JavaScript.
Technical constraints:
- The trigger element should be a button (semantic, focusable).
- The dropdown list is positioned absolutely below the trigger.
- Use opacity + visibility + transform for a smooth reveal animation.
- Use :hover on the parent container AND :focus-within so the menu stays open when tabbing through links.
- Use oklch() for all colors. Add a box-shadow for depth.
- Transition duration should be short (100–200ms) for responsiveness.
Accessibility:
- The trigger must be a <button> element, not a <div>.
- Links inside the dropdown must be focusable and reachable via Tab.
- :focus-within keeps the menu open while any child has focus.
- Add a small gap between trigger and list to prevent accidental close on mouse move.Why this matters in 2026
Dropdown menus are one of the most common UI patterns, yet many implementations fail on keyboard accessibility. The :focus-within pseudo-class changed the game — it keeps the dropdown open as long as any descendant element has focus. Combined with :hover, you get a dropdown that works for both mouse and keyboard users without a single line of JavaScript.
The logic
The dropdown list starts with opacity: 0, visibility: hidden, and transform: translateY(-0.5rem). When the parent is hovered or any child receives focus (:focus-within), these flip to visible — the list fades in and slides down. Using both opacity and visibility is important: opacity alone would leave an invisible but interactive element blocking clicks. visibility: hidden removes it from the interaction layer. The transition on visibility delays its toggle to match the opacity fade.
Accessibility & performance
Using a <button> as the trigger means screen readers announce it as an interactive element. :focus-within ensures keyboard users can Tab through all dropdown links without the menu closing. For full ARIA compliance, add aria-expanded and aria-haspopup="true" on the button (this requires a small amount of JS to toggle aria-expanded). Transitions use only opacity and transform — both GPU-composited.