Dropdown Menu

Displays a menu to the user — such as a set of actions or functions — triggered by a button.

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-expanded and manages keyboard navigation.
<div data-popover aria-hidden="true">
Menu content popover. Set data-side="top|right|bottom|left" and data-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-indicator for the checked icon.
<div role="menuitemradio" aria-checked="true">
Radio-style item. Add a child with data-indicator for 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:initialized event 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

Shortcuts

Icons

Checkboxes

Radio Group

Destructive

RTL

Dropdown menus support document direction. Set dir="rtl" on the dropdown root or a parent element. Use logical alignment data attributes where possible.

API Reference

.dropdown-menu
Root component class. Must wrap the trigger and the [data-popover] content.
[data-popover]
Inline menu popover. Supports data-side and data-align through 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, and data-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".