Dropdown menu
wu-dropdown is a menu of links and buttons in a native popover. A button with popovertarget opens and closes it, with no JavaScript. Esc and a click outside close it. Button items close it too when they point at the menu with popovertargetaction="hide".
How to make a dropdown menu without JavaScript
Section titled “How to make a dropdown menu without JavaScript”Make the menu a <div> with the popover attribute, then give a button the menu’s id in popovertarget. Clicking the button opens the menu, and a click outside or the Esc key closes it. This is the HTML popover feature built into the browser. The menu opens under the button. The full demo is under Default.
<button class="wu-btn" type="button" popovertarget="my-menu">Actions</button><div class="wu-dropdown" id="my-menu" popover> <a class="wu-dropdown-item" href="/edit">Edit</a> <a class="wu-dropdown-item" href="/share">Share</a></div>| Class, attribute or variable | Effect |
|---|---|
wu-dropdown |
Root, on an element with popover. Raised panel with a border and a shadow |
wu-dropdown-item |
Part, on <a> or <button>. Full-width row with a hover tint and an inset focus ring |
wu-dropdown-separator |
Part, on <hr>. Line between groups of items |
svg in an item |
The app’s own icon, sized to the text |
danger on an item |
Danger text and tint |
disabled or aria-disabled="true" on an item |
Dimmed, not clickable |
popovertarget with popovertargetaction="hide" on a button item |
Closes the menu after the item’s click handler runs |
inline-end |
Aligns the menu’s inline end with the trigger’s, where anchor positioning exists |
animated |
Fades and slides in and out, opt-in |
--wu-dropdown--MinWidth |
Minimum width, 10rem by default |
--wu-dropdown--MaxHeight |
Height after which the menu scrolls, 20rem by default |
Default
Section titled “Default”<button class="wu-btn" type="button" popovertarget="dropdown-actions">Actions</button><div class="wu-dropdown" id="dropdown-actions" popover> <button class="wu-dropdown-item" type="button" popovertarget="dropdown-actions" popovertargetaction="hide">Edit</button> <button class="wu-dropdown-item" type="button" popovertarget="dropdown-actions" popovertargetaction="hide">Duplicate</button> <button class="wu-dropdown-item" type="button" popovertarget="dropdown-actions" popovertargetaction="hide" disabled>Archive</button> <hr class="wu-dropdown-separator"> <button class="wu-dropdown-item danger" type="button" popovertarget="dropdown-actions" popovertargetaction="hide">Delete</button></div>Caret trigger
Section titled “Caret trigger”Add caret to the trigger button. The caret turns while the menu is open, and an empty caret button works as a compact trigger.
<div class="flex wrap x-center gap-2"> <button class="wu-btn caret" type="button" popovertarget="dropdown-caret">Options</button> <div class="wu-dropdown" id="dropdown-caret" popover> <button class="wu-dropdown-item" type="button" popovertarget="dropdown-caret" popovertargetaction="hide">Rename</button> <button class="wu-dropdown-item" type="button" popovertarget="dropdown-caret" popovertargetaction="hide">Move</button> </div> <button class="wu-btn caret" type="button" popovertarget="dropdown-caret-only" aria-label="More options"></button> <div class="wu-dropdown" id="dropdown-caret-only" popover> <button class="wu-dropdown-item" type="button" popovertarget="dropdown-caret-only" popovertargetaction="hide">Share</button> <button class="wu-dropdown-item" type="button" popovertarget="dropdown-caret-only" popovertargetaction="hide">Copy link</button> </div></div>Invoker commands
Section titled “Invoker commands”commandfor with command="toggle-popover" works as the trigger too.
<button class="wu-btn" type="button" commandfor="dropdown-sort" command="toggle-popover">Sort</button><div class="wu-dropdown" id="dropdown-sort" popover> <button class="wu-dropdown-item" type="button" popovertarget="dropdown-sort" popovertargetaction="hide">Newest first</button> <button class="wu-dropdown-item" type="button" popovertarget="dropdown-sort" popovertargetaction="hide">Oldest first</button> <button class="wu-dropdown-item" type="button" popovertarget="dropdown-sort" popovertargetaction="hide">Name</button></div>Placement
Section titled “Placement”The menu opens under its trigger, aligned to its inline start, and flips above or to the other side when there is no room. inline-end aligns it to the trigger’s inline end instead, as the split button does.
The alignment follows the menu’s own direction, so a menu inside a dir="rtl" section of a left-to-right page opens toward the left, like the trigger’s text.
<button class="wu-btn" type="button" popovertarget="menu1" style="anchor-name: --menu1">Actions</button><div class="wu-dropdown" id="menu1" popover style="position-anchor: --menu1">...</div>An svg placed directly in an item is sized to the text and keeps its own fill or stroke. Mark it aria-hidden="true": the item text names the action.
<button class="wu-btn" type="button" popovertarget="dropdown-icons">File</button><div class="wu-dropdown" id="dropdown-icons" popover> <button class="wu-dropdown-item" type="button" popovertarget="dropdown-icons" popovertargetaction="hide"><svg viewBox="0 0 16 16" fill="none" stroke="currentcolor" stroke-width="1.5" aria-hidden="true"><path d="M8 2v9M4 7l4 4 4-4M3 14h10"/></svg>Download</button> <button class="wu-dropdown-item" type="button" popovertarget="dropdown-icons" popovertargetaction="hide">Rename</button></div>Right to left
Section titled “Right to left”The menu aligns to the trigger’s inline start, which is its right edge in right-to-left text, and the item text and icons follow the direction.
<div dir="rtl"> <button class="wu-btn caret" type="button" popovertarget="dropdown-rtl">خيارات</button> <div class="wu-dropdown" id="dropdown-rtl" popover> <button class="wu-dropdown-item" type="button" popovertarget="dropdown-rtl" popovertargetaction="hide">إعادة تسمية</button> <button class="wu-dropdown-item" type="button" popovertarget="dropdown-rtl" popovertargetaction="hide">نقل</button> <hr class="wu-dropdown-separator"> <button class="wu-dropdown-item danger" type="button" popovertarget="dropdown-rtl" popovertargetaction="hide">حذف</button> </div></div>Animation
Section titled “Animation”The animated word fades the menu in and slides it a little away from its trigger, down under it or up when it flips above, and reverses on close. wu-animated on an ancestor turns it on for every dropdown inside. Motion runs only under prefers-reduced-motion: no-preference.
<button class="wu-btn" type="button" popovertarget="dropdown-animated">Account</button><div class="wu-dropdown animated" id="dropdown-animated" popover> <button class="wu-dropdown-item" type="button" popovertarget="dropdown-animated" popovertargetaction="hide">Profile</button> <button class="wu-dropdown-item" type="button" popovertarget="dropdown-animated" popovertargetaction="hide">Settings</button> <hr class="wu-dropdown-separator"> <button class="wu-dropdown-item" type="button" popovertarget="dropdown-animated" popovertargetaction="hide">Sign out</button></div>Accessibility
Section titled “Accessibility”- Items are real links and buttons: Tab moves between them, Enter activates them. The kit adds no arrow-key handling.
- A button item with
popovertargetaction="hide"closes the menu after it runs, and focus returns to the trigger. Itsclicklisteners still fire first. - The trigger keeps its hover look while the menu is open; a
carettrigger turns its caret. - Use a
<button>as the trigger, sopopovertargetorcommandforlinks it to the menu. - Keyboard focus draws an inset ring, so the scrolling menu does not clip it.
Load only the dropdown
Section titled “Load only the dropdown”@use "@wovenui/components/dropdown";The dropdown needs no other component. The examples above also use the button.