2 Pure CSS6 with JSPublished 2026-10-04Free · no sign-up
Eight dropdowns for apps and websites: an action menu button, a navigation dropdown that opens on hover and focus, a mega menu, a no-JavaScript popover menu, an account menu, a split button, a language switcher and a right-click context menu.
A menu button that opens a list of actions, following the ARIA menu button pattern: aria-haspopup, aria-expanded, role="menu" and menuitems. Arrow keys move through items, Escape closes and returns focus, and clicking outside closes it.
const mb = document.getElementById('mb-b'), mm = document.getElementById('mb-m'), items = [...mm.querySelectorAll('[role=menuitem]')];
function set(o, focusFirst) { mm.hidden = !o; mb.setAttribute('aria-expanded', String(o)); if (o && focusFirst) items[0].focus(); }
mb.addEventListener('click', () => set(mm.hidden, true));
mb.addEventListener('keydown', e => { if (e.key === 'ArrowDown') { e.preventDefault(); set(true, true); } });
mm.addEventListener('keydown', e => {
const i = items.indexOf(document.activeElement);
if (e.key === 'ArrowDown') { e.preventDefault(); items[(i + 1) % items.length].focus(); }
if (e.key === 'ArrowUp') { e.preventDefault(); items[(i - 1 + items.length) % items.length].focus(); }
if (e.key === 'Escape' || e.key === 'Tab') { set(false); if (e.key === 'Escape') mb.focus(); }
});
items.forEach(it => it.addEventListener('click', () => { set(false); mb.focus(); }));
document.addEventListener('click', e => { if (!e.target.closest('.mb')) set(false); });
02. Navigation Dropdown on Hover and Focus
NavigationCSS
A navbar where a link reveals a submenu on hover and also when anything inside it has keyboard focus, using :focus-within. The submenu links are a normal list, so Tab reaches every one, and a short delay stops the menu closing when the pointer slips.
A wide dropdown with several columns of links and a promotional card, as used by large stores. The panel is toggled by a disclosure button with aria-expanded, which is simpler and more predictable than menu roles for site navigation.
A dropdown that uses the popover attribute and CSS anchor positioning: no JavaScript, light dismiss on outside click, Escape to close and placement directly under its button. Without anchor support it still opens, just in the browser's default position.
The account menu found in app headers: an avatar button opens a panel with the person's name and email, a few links and a sign-out button separated by a divider. The avatar button's accessible name includes the person's name.
View code
<div class="ua">
<button class="av" aria-expanded="false" aria-controls="ua-p" aria-label="Account menu for Sara Khan">SK</button>
<div id="ua-p" class="ua-p" hidden>
<p class="who"><b>Sara Khan</b><span>sara@clayworks.shop</span></p>
<a href="#">Profile</a><a href="#">Billing</a><a href="#">Team settings</a>
<hr>
<button class="out">Sign out</button>
</div>
</div>
const av = document.querySelector('.av'), up = document.getElementById('ua-p');
function set(o) { up.hidden = !o; av.setAttribute('aria-expanded', String(o)); }
av.addEventListener('click', () => set(up.hidden));
document.addEventListener('keydown', e => { if (e.key === 'Escape' && !up.hidden) { set(false); av.focus(); } });
document.addEventListener('click', e => { if (!e.target.closest('.ua')) set(false); });
set(true);
06. Split Button Dropdown
ButtonActions
A primary action with an attached arrow that opens related options, like Publish with Schedule and Save as draft. The two halves are separate buttons with their own names, so it is clear what each one does when reached with Tab.
View code
<div class="spb">
<div class="grp"><button class="main">Publish</button><button class="arr" aria-label="More publish options" aria-expanded="false" aria-controls="spb-m"></button></div>
<ul id="spb-m" hidden><li><button>Schedule for later</button></li><li><button>Save as draft</button></li><li><button>Preview</button></li></ul>
</div>
const arr = document.querySelector('.arr'), sm = document.getElementById('spb-m');
function set(o) { sm.hidden = !o; arr.setAttribute('aria-expanded', String(o)); }
arr.addEventListener('click', () => { set(sm.hidden); if (!sm.hidden) sm.querySelector('button').focus(); });
document.addEventListener('keydown', e => { if (e.key === 'Escape' && !sm.hidden) { set(false); arr.focus(); } });
document.addEventListener('click', e => { if (!e.target.closest('.spb')) set(false); });
07. Language Switcher Dropdown
i18nLinks
A language switcher where each option is a link to the translated page, marked with lang and hreflang attributes so screen readers pronounce each language name correctly. The current language is shown on the button and marked with aria-current.
A custom context menu that opens where you right-click inside a file area, and also from the keyboard with Shift+F10 or the context menu key. It stays inside the visible area, and Escape or a click elsewhere closes it.
const area = document.querySelector('.area'), ctx = document.querySelector('.ctx');
function openAt(x, y) {
ctx.hidden = false;
const r = ctx.getBoundingClientRect();
ctx.style.left = Math.min(x, innerWidth - r.width - 8) + 'px';
ctx.style.top = Math.min(y, innerHeight - r.height - 8) + 'px';
ctx.querySelector('button').focus();
}
area.addEventListener('contextmenu', e => { e.preventDefault(); openAt(e.clientX, e.clientY); });
area.addEventListener('keydown', e => { if ((e.shiftKey && e.key === 'F10') || e.key === 'ContextMenu') { e.preventDefault(); const r = area.getBoundingClientRect(); openAt(r.left + 20, r.top + 20); } });
document.addEventListener('keydown', e => { if (e.key === 'Escape' && !ctx.hidden) { ctx.hidden = true; area.focus(); } });
document.addEventListener('click', e => { if (!e.target.closest('.ctx')) ctx.hidden = true; });
About these CSS Dropdown Menus
Navigation or menu?
For site navigation, a simple disclosure button with aria-expanded that shows a list of links is easiest to use. The ARIA role="menu" pattern is meant for application actions like Edit and Delete, and it brings stricter keyboard expectations.
Keyboard and Escape
Every dropdown here opens from the keyboard, closes on Escape and returns focus to its trigger. Menus with role=menu also move focus between items with the arrow keys.
Hover menus need focus too
A dropdown that opens on :hover must also open on :focus-within, or keyboard users cannot reach its links. A short transition delay stops it closing when the pointer briefly leaves.
The popover attribute
The popover attribute with popovertarget gives light dismiss and Escape handling for free. Combined with CSS anchor positioning it places the menu under its button without JavaScript.
Frequently asked questions
Which dropdowns work without JavaScript?
The hover nav dropdown and the popover menu.
Should a mega menu open on hover?
Opening on click is more predictable, especially on touch screens. The mega demo uses a disclosure button.
How do I keep a menu on screen?
Measure the menu and clamp its position to the viewport, as the context menu demo does.