Dropdown Menu
Displays a menu to the user — such as a set of actions or functions — triggered by a button.
<div id="demo-dropdown-menu" class="dropdown-menu">
<button type="button" id="demo-dropdown-menu-trigger" aria-haspopup="menu" aria-controls="demo-dropdown-menu-menu" aria-expanded="false" class="btn-outline">Open</button>
<div id="demo-dropdown-menu-popover" data-popover aria-hidden="true" class="min-w-56">
<div role="menu" id="demo-dropdown-menu-menu" aria-labelledby="demo-dropdown-menu-trigger">
<div role="group" aria-labelledby="demo-dropdown-account">
<div role="heading" id="demo-dropdown-account">My Account</div>
<div role="menuitem">
Profile
<span data-shortcut>⇧⌘P</span>
</div>
<div role="menuitem">
Billing
<span data-shortcut>⌘B</span>
</div>
<div role="menuitem">Settings</div>
<div role="menuitem">Keyboard shortcuts</div>
</div>
<hr role="separator" />
<div role="menuitem">GitHub</div>
<div role="menuitem">Support</div>
<div role="menuitem" aria-disabled="true">API</div>
<hr role="separator" />
<div role="menuitem">Log out</div>
</div>
</div>
</div>
Usage
Basecoat dropdown menus are inline-positioned relative to the .dropdown-menu wrapper. This differs from shadcn/ui's portalled Base UI implementation, but keeps the markup dependency-free and matches Basecoat's current popover/select positioning model.
HTML + JavaScript
Step 1: Include the JavaScript file
You can either include the JavaScript file for all the components, or just the one for this component by adding this to the <head> of your page:
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0/dist/js/basecoat.min.js" defer></script>
<script src="https://cdn.jsdelivr.net/npm/basecoat-css@1.0.0/dist/js/dropdown-menu.min.js" defer></script>
Step 2: Add your dropdown menu HTML
<div id="demo-dropdown-menu" class="dropdown-menu">
<button type="button" id="demo-dropdown-menu-trigger" aria-haspopup="menu" aria-controls="demo-dropdown-menu-menu" aria-expanded="false" class="btn-outline">Open</button>
<div id="demo-dropdown-menu-popover" data-popover aria-hidden="true" class="min-w-56">
<div role="menu" id="demo-dropdown-menu-menu" aria-labelledby="demo-dropdown-menu-trigger">
<div role="group" aria-labelledby="demo-dropdown-account">
<div role="heading" id="demo-dropdown-account">My Account</div>
<div role="menuitem">
Profile
<span data-shortcut>⇧⌘P</span>
</div>
<div role="menuitem">
Billing
<span data-shortcut>⌘B</span>
</div>
<div role="menuitem">Settings</div>
<div role="menuitem">Keyboard shortcuts</div>
</div>
<hr role="separator" />
<div role="menuitem">GitHub</div>
<div role="menuitem">Support</div>
<div role="menuitem" aria-disabled="true">API</div>
<hr role="separator" />
<div role="menuitem">Log out</div>
</div>
</div>
</div>
HTML structure
<div class="dropdown-menu">- Relative wrapper for the trigger and inline menu content.
<button aria-haspopup="menu" aria-expanded="false">- Trigger button. The script toggles
aria-expandedand manages keyboard navigation. <div data-popover aria-hidden="true">- Menu content popover. Set
data-side="top|right|bottom|left"anddata-align="start|center|end"to control placement.<div role="menu">- Container for menu items, groups, labels, and separators.
<div role="group" aria-labelledby="{ HEADING_ID }">Optional- Groups related menu items.
<div role="heading" id="{ HEADING_ID }">Optional- Group heading/label.
<div role="menuitem">- Standard action item. Use
aria-disabled="true"for disabled items. <div role="menuitemcheckbox" aria-checked="true">- Checkbox-style item. Add a child with
data-indicatorfor the checked icon. <div role="menuitemradio" aria-checked="true">- Radio-style item. Add a child with
data-indicatorfor the selected icon. <span data-shortcut>Optional- Shortcut hint aligned to the inline end of the item.
<hr role="separator">Optional- Separator between groups or options.
JavaScript API
basecoat:initialized- Once the component is initialized, it dispatches a custom non-bubbling
basecoat:initializedevent on itself. basecoat:popover- When the menu opens, the component dispatches a custom event on
document. Other popover-based components listen for this to close any open popovers. dropdown.open()- Opens the menu.
dropdown.close()- Closes the menu.
dropdown.toggle()- Toggles the menu.
dropdown.refresh()- Rescans menu items after changing children inside the existing
role="menu"element. window.basecoat.refresh(dropdown)- Calls the component refresh method through the global dispatcher.
Jinja and Nunjucks
You can use the dropdown_menu() Nunjucks or Jinja macro for this component.
{% call dropdown_menu(
id="dropdown-menu",
trigger="Open",
trigger_attrs={"class": "btn-outline"},
popover_attrs={"class": "min-w-56"}
) %}
<div role="group" aria-labelledby="account-options">
<div role="heading" id="account-options">My Account</div>
<div role="menuitem">Profile</div>
<div role="menuitem">Billing</div>
</div>
<hr role="separator">
<div role="menuitem">Team</div>
<div role="menuitem">Subscription</div>
{% endcall %}
Examples
Basic
<div id="dropdown-basic" class="dropdown-menu">
<button type="button" id="dropdown-basic-trigger" aria-haspopup="menu" aria-controls="dropdown-basic-menu" aria-expanded="false" class="btn-outline">Open</button>
<div id="dropdown-basic-popover" data-popover aria-hidden="true" class="min-w-56">
<div role="menu" id="dropdown-basic-menu" aria-labelledby="dropdown-basic-trigger">
<div role="group" aria-labelledby="dropdown-basic-account">
<div role="heading" id="dropdown-basic-account">My Account</div>
<div role="menuitem">Profile</div>
<div role="menuitem">Billing</div>
<div role="menuitem">Settings</div>
</div>
<hr role="separator" />
<div role="menuitem">Team</div>
<div role="menuitem" aria-disabled="true">API</div>
</div>
</div>
</div>
Shortcuts
<div id="dropdown-shortcuts" class="dropdown-menu">
<button type="button" id="dropdown-shortcuts-trigger" aria-haspopup="menu" aria-controls="dropdown-shortcuts-menu" aria-expanded="false" class="btn-outline">Open</button>
<div id="dropdown-shortcuts-popover" data-popover aria-hidden="true" class="min-w-56">
<div role="menu" id="dropdown-shortcuts-menu" aria-labelledby="dropdown-shortcuts-trigger">
<div role="menuitem">
New Tab
<span data-shortcut>⌘T</span>
</div>
<div role="menuitem">
New Window
<span data-shortcut>⌘N</span>
</div>
<div role="menuitem">
New Private Window
<span data-shortcut>⇧⌘N</span>
</div>
<hr role="separator" />
<div role="menuitem">
Print
<span data-shortcut>⌘P</span>
</div>
</div>
</div>
</div>
Icons
<div id="dropdown-icons" class="dropdown-menu">
<button type="button" id="dropdown-icons-trigger" aria-haspopup="menu" aria-controls="dropdown-icons-menu" aria-expanded="false" class="btn-outline">Open</button>
<div id="dropdown-icons-popover" data-popover aria-hidden="true" class="min-w-56">
<div role="menu" id="dropdown-icons-menu" aria-labelledby="dropdown-icons-trigger">
<div role="menuitem">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" />
<circle cx="12" cy="7" r="4" />
</svg>
Profile
</div>
<div role="menuitem">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect width="20" height="14" x="2" y="5" rx="2" />
<line x1="2" x2="22" y1="10" y2="10" />
</svg>
Billing
</div>
<div role="menuitem">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" />
<circle cx="12" cy="12" r="3" />
</svg>
Settings
</div>
<hr role="separator" />
<div role="menuitem">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
<polyline points="16 17 21 12 16 7" />
<line x1="21" x2="9" y1="12" y2="12" />
</svg>
Log out
</div>
</div>
</div>
</div>
Checkboxes
<div id="dropdown-checkboxes" class="dropdown-menu">
<button type="button" id="dropdown-checkboxes-trigger" aria-haspopup="menu" aria-controls="dropdown-checkboxes-menu" aria-expanded="false" class="btn-outline">Open</button>
<div id="dropdown-checkboxes-popover" data-popover aria-hidden="true" class="min-w-56">
<div role="menu" id="dropdown-checkboxes-menu" aria-labelledby="dropdown-checkboxes-trigger">
<div role="group" aria-labelledby="dropdown-checkboxes-label">
<div role="heading" id="dropdown-checkboxes-label">Appearance</div>
<div role="menuitemcheckbox" aria-checked="true">
<span data-indicator>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
</span>
Status Bar
<span data-shortcut>⌘S</span>
</div>
<div role="menuitemcheckbox" aria-checked="false">
<span data-indicator>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
</span>
Activity Bar
<span data-shortcut>⌘A</span>
</div>
<div role="menuitemcheckbox" aria-checked="false" aria-disabled="true">
<span data-indicator>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
</span>
Panel
<span data-shortcut>⌘P</span>
</div>
</div>
</div>
</div>
</div>
<script>
(() => {
const dropdownMenu = document.querySelector("#dropdown-checkboxes");
const checkboxes = dropdownMenu.querySelectorAll('[role="menuitemcheckbox"]');
checkboxes.forEach((checkbox) => {
checkbox.addEventListener("click", () => {
if (checkbox.getAttribute("aria-disabled") === "true") return;
checkbox.setAttribute("aria-checked", checkbox.getAttribute("aria-checked") !== "true");
});
});
})();
</script>
Radio Group
<div id="dropdown-radio-group" class="dropdown-menu">
<button type="button" id="dropdown-radio-group-trigger" aria-haspopup="menu" aria-controls="dropdown-radio-group-menu" aria-expanded="false" class="btn-outline">Open</button>
<div id="dropdown-radio-group-popover" data-popover aria-hidden="true" class="min-w-56">
<div role="menu" id="dropdown-radio-group-menu" aria-labelledby="dropdown-radio-group-trigger">
<div role="group" aria-labelledby="dropdown-radio-label">
<div role="heading" id="dropdown-radio-label">Panel Position</div>
<div role="menuitemradio" aria-checked="false">
<span data-indicator>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
</span>
Top
</div>
<div role="menuitemradio" aria-checked="true">
<span data-indicator>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
</span>
Bottom
</div>
<div role="menuitemradio" aria-checked="false">
<span data-indicator>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg>
</span>
Right
</div>
</div>
</div>
</div>
</div>
<script>
(() => {
const dropdownMenu = document.querySelector("#dropdown-radio-group");
const radios = dropdownMenu.querySelectorAll('[role="menuitemradio"]');
radios.forEach((radio) => {
radio.addEventListener("click", () => {
radios.forEach((item) => item.setAttribute("aria-checked", "false"));
radio.setAttribute("aria-checked", "true");
});
});
})();
</script>
Destructive
<div id="dropdown-destructive" class="dropdown-menu">
<button type="button" id="dropdown-destructive-trigger" aria-haspopup="menu" aria-controls="dropdown-destructive-menu" aria-expanded="false" class="btn-outline">Open</button>
<div id="dropdown-destructive-popover" data-popover aria-hidden="true" class="min-w-56">
<div role="menu" id="dropdown-destructive-menu" aria-labelledby="dropdown-destructive-trigger">
<div role="menuitem">Edit</div>
<div role="menuitem">Duplicate</div>
<hr role="separator" />
<div role="menuitem" data-variant="destructive">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 6h18" />
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
<line x1="10" x2="10" y1="11" y2="17" />
<line x1="14" x2="14" y1="11" y2="17" />
</svg>
Delete
</div>
</div>
</div>
</div>
RTL
Dropdown menus support document direction. Set dir="rtl" on the dropdown root or a parent element. Use logical alignment data attributes where possible.
<div dir="rtl">
<div id="dropdown-rtl" class="dropdown-menu">
<button type="button" id="dropdown-rtl-trigger" aria-haspopup="menu" aria-controls="dropdown-rtl-menu" aria-expanded="false" class="btn-outline">فتح</button>
<div id="dropdown-rtl-popover" data-popover aria-hidden="true" class="min-w-56" data-align="end">
<div role="menu" id="dropdown-rtl-menu" aria-labelledby="dropdown-rtl-trigger">
<div role="heading" id="dropdown-rtl-label">الحساب</div>
<div role="menuitem">
الملف الشخصي
<span data-shortcut>⌘P</span>
</div>
<div role="menuitem">
الإعدادات
<span data-shortcut>⌘S</span>
</div>
<hr role="separator" />
<div role="menuitem" data-variant="destructive">حذف</div>
</div>
</div>
</div>
</div>
API Reference
.dropdown-menu- Root component class. Must wrap the trigger and the
[data-popover]content. [data-popover]- Inline menu popover. Supports
data-sideanddata-alignthrough the shared popover positioning rules. [role="menu"]- Menu container referenced by the trigger's
aria-controls. [role="menuitem"]- Action item. Supports
aria-disabled="true",data-inset, anddata-variant="destructive". [role="menuitemcheckbox"]- Checkbox item. Use
aria-checked="true|false"and an optional[data-indicator]child. [role="menuitemradio"]- Radio item. Use
aria-checked="true|false"and an optional[data-indicator]child. [role="heading"]- Group label. Supports
data-inset. [role="separator"]- Visual separator between groups or actions.
[data-shortcut]- Shortcut hint aligned to the inline end of a menu item.
[data-indicator]- Indicator slot for checkbox and radio menu items. It becomes visible when the item has
aria-checked="true".