Dropdown Menu
Action menu using the native HTML Popover API. Click the button to open it. Click outside or press Escape to dismiss.
Markup
Section titled “Markup”Wrap the trigger and menu in .af-dropdown. That names the button as a CSS anchor so the open menu sits underneath — popovertarget must still match the menu id. The empty popover attribute is popover="auto": one open at a time, light-dismissed.
<div class="af-dropdown">
<button type="button" class="af-btn" popovertarget="dropdown-menu" aria-haspopup="menu">Menu</button>
<ul id="dropdown-menu" popover class="af-dropdown-menu" role="menu">
<li>
<a href="#" role="menuitem">Item 1</a>
</li>
<li>
<a href="#" role="menuitem">Item 2</a>
</li>
<li>
<button type="button" role="menuitem">Item 3</button>
</li>
</ul>
</div>Alignment
Section titled “Alignment”Default is inline-start (left in LTR). Add af-is-end on .af-dropdown to align the menu’s end edge to the button’s end edge — typical for an account menu in a header. af-is-start is the explicit form of the default.
<div class="af-spread">
<div class="af-dropdown">
<button type="button" class="af-btn" popovertarget="dropdown-menu" aria-haspopup="menu">Menu</button>
<ul id="dropdown-menu" popover class="af-dropdown-menu" role="menu">
<li>
<a href="#" role="menuitem">Item 1</a>
</li>
<li>
<a href="#" role="menuitem">Item 2</a>
</li>
</ul>
</div>
<div class="af-dropdown af-is-end">
<button type="button" class="af-btn" popovertarget="dropdown-menu-end" aria-haspopup="menu">Account</button>
<ul id="dropdown-menu-end" popover class="af-dropdown-menu" role="menu">
<li>
<a href="#" role="menuitem">Profile</a>
</li>
<li>
<a href="#" role="menuitem">Settings</a>
</li>
<li>
<button type="button" role="menuitem">Sign out</button>
</li>
</ul>
</div>
</div>Put the modifier on .af-dropdown, or on .af-dropdown-menu if you are not using the wrapper.
CSS classes
Section titled “CSS classes”.af-dropdown— wraps trigger + menu; scopes the CSS anchor so two dropdowns on a page don’t collide.af-dropdown.af-is-start— align menu to the trigger’s inline-start edge (default).af-dropdown.af-is-end— align menu to the trigger’s inline-end edge.af-dropdown-menu— menu container.af-dropdown-menu-open— show the menu when you are not usingpopover(alternative to removinghidden)
Without popover
Section titled “Without popover”Toggle [hidden] or .af-dropdown-menu-open yourself, and keep the trigger in a position: relative parent. Supporting browsers already do that work with popover.
<div style="position: relative;">
<button type="button" class="af-btn" aria-haspopup="menu" aria-expanded="false">Menu</button>
<ul class="af-dropdown-menu" role="menu" hidden>
<li>
<a href="#" role="menuitem">Item 1</a>
</li>
</ul>
</div>Accessibility
Section titled “Accessibility”- Use a native
<button>trigger and a<ul>menu.role="menu"/role="menuitem"are required — there is no native menu element. popovertargetsetsaria-expandedon the invoker. Do not set it yourself on the popover path.- Popover handles open/close, click-outside, and Escape. Arrow keys, Home/End, and typeahead stay in your JavaScript or kit.
Browser support
Section titled “Browser support”Toggle works wherever Popover does. Sitting the menu under the button is CSS anchor positioning — progressive enhancement, same as the styled Select picker.
| Feature | Chrome | Edge | Safari | Firefox |
|---|---|---|---|---|
popover Toggle, light-dismiss, and Escape with no JavaScript | 114 Supported from 114 | 114 Supported from 114 | 17 Supported from 17 | 125 Supported from 125 |
anchor() Sits the open menu under the button (flips above if needed) | 125 Supported from 125 | 125 Supported from 125 | 26 Supported from 26 | 147 Supported from 147 |
Safari versions cover iOS Safari too. Without support: the button does nothing if Popover is missing. If Popover works but anchor positioning does not, the menu still opens — centered on the viewport, from the user-agent stylesheet. Versions checked against MDN browser-compat-data, August 2026. See Browser Support for Airframe's baseline.