<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="account-options">
<div role="heading" id="account-options">My Account</div>
<div role="menuitem">
Profile
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
</div>
<div role="menuitem">
Billing
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
</div>
<div role="menuitem">
Settings
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
</div>
<div role="menuitem">
Keyboard shortcuts
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘K</span>
</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">
Logout
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
</div>
</div>
</div>
</div>
Usage
HTML + JavaScript
Step 1: Include the JavaScript files
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@0.2.5/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="account-options">
<div role="heading" id="account-options">My Account</div>
<div role="menuitem">
Profile
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
</div>
<div role="menuitem">
Billing
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
</div>
<div role="menuitem">
Settings
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
</div>
<div role="menuitem">
Keyboard shortcuts
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘K</span>
</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">
Logout
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
</div>
</div>
</div>
</div>
HTML structure
<div class="dropdown-menu">
- Wraps around the entire component.
<button type="button" popovertarget="{ POPOVER_ID }">
-
The trigger to open the popover, can also have the following attributes:
id="{BUTTON_ID}"
: linked to by thearia-labelledby
attribute of the listbox.aria-haspopup="menu"
: indicates that the button opens a menu.aria-controls="{ MENU_ID }"
: points to the menu's id.aria-expanded="false"
: tracks the popover's state.
<div popover class="popover" id="{ POPOVER_ID }">
- As with the Popover component, you can set up the side and alignment of the popover using the
data-side
anddata-align
attributes.<div role="menu">
- The menu containing the options. Should have the following attributes:
id="{ MENU_ID }"
: refered to by thearia-controls
attribute of the trigger.aria-labelledby="{ BUTTON_ID }"
: linked to by the button'sid
attribute.
<button role="menuitem">
- Menu item.
<button role="menuitemcheckbox">
- Menu item with a checkbox.
<button role="menuitemradio">
- Menu item with a radio button.
<hr role="separator">
Optional- Separator between groups/options.
<div role="group">
Optional- Group of options, can have a
aria-labelledby
attribute to link to a heading. <span role="heading">
- Group heading, must have an
id
attribute if you use thearia-labelledby
attribute on the group.
JavaScript events
basecoat:initialized
- Once the component is fully initialized, it dispatches a custom (non-bubbling)
basecoat:initialized
event on itself. basecoat:popover
- When the popover opens, the component dispatches a custom (non-bubbling)
basecoat:popover
event ondocument
. Other popover components (Combobox, Dropdown Menu, Popover and Select) listen for this to close any open popovers.
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
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
</div>
<div role="menuitem">
Billing
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
</div>
<div role="menuitem">
Settings
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
</div>
<div role="menuitem">
Keyboard shortcuts
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘K</span>
</div>
</div>
<hr role="separator">
<div role="menuitem">
GitHub
</div>
<div role="menuitem">
Support
</div>
<div role="menuitem" disabled>
API
</div>
<hr role="separator">
<div role="menuitem">
Logout
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
</div>
{% endcall %}
Examples
Checkboxes
<div id="dropdown-menu-checkboxes" class="dropdown-menu">
<button type="button" id="dropdown-menu-checkboxes-trigger" aria-haspopup="menu" aria-controls="dropdown-menu-checkboxes-menu" aria-expanded="false" class="btn-outline">Open</button>
<div id="dropdown-menu-checkboxes-popover" data-popover aria-hidden="true" class="min-w-56">
<div role="menu" id="dropdown-menu-checkboxes-menu" aria-labelledby="dropdown-menu-checkboxes-trigger">
<div role="group" aria-labelledby="account-options">
<div role="heading" id="account-options">Account Options</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="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" />
<circle cx="12" cy="7" r="4" />
</svg>
Profile
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
</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
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
</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
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
</div>
</div>
<hr role="separator" />
<div role="group" aria-labelledby="appearance-options">
<div role="heading" id="appearance-options">Appearance</div>
<div role="menuitemcheckbox" aria-checked="true" class="group">
<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" class="invisible group-aria-checked:visible" aria-hidden="true" focusable="false"><path d="M20 6 9 17l-5-5" /></svg>
Status Bar
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
</div>
<div role="menuitemcheckbox" aria-checked="false" class="group" aria-disabled="true">
<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" class="invisible group-aria-checked:visible" aria-hidden="true" focusable="false"><path d="M20 6 9 17l-5-5" /></svg>
Activity Bar
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
</div>
<div role="menuitemcheckbox" aria-checked="false" class="group">
<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" class="invisible group-aria-checked:visible" aria-hidden="true" focusable="false"><path d="M20 6 9 17l-5-5" /></svg>
Panel
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
</div>
</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>
Logout
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
</div>
</div>
</div>
</div>
<script>
(() => {
const dropdownMenu = document.querySelector("#dropdown-menu-checkboxes");
const checkboxes = dropdownMenu.querySelectorAll('div[role="menuitemcheckbox"]');
checkboxes.forEach((checkbox) => {
checkbox.addEventListener("click", () => {
const isChecked = checkbox.getAttribute("aria-checked") === "true";
checkbox.setAttribute("aria-checked", !isChecked);
});
});
})();
</script>
Radio Group
<div id="dropdown-menu-radio-group" class="dropdown-menu">
<button type="button" id="dropdown-menu-radio-group-trigger" aria-haspopup="menu" aria-controls="dropdown-menu-radio-group-menu" aria-expanded="false" class="btn-outline">Open</button>
<div id="dropdown-menu-radio-group-popover" data-popover aria-hidden="true" class="min-w-56">
<div role="menu" id="dropdown-menu-radio-group-menu" aria-labelledby="dropdown-menu-radio-group-trigger">
<div role="group" aria-labelledby="position-options">
<span id="position-options" role="heading">Panel Position</span>
<hr role="separator" />
<div role="menuitemradio" aria-checked="false" class="group">
<div class="size-4 flex items-center justify-center">
<div class="size-2 rounded-full bg-foreground invisible group-aria-checked:visible" aria-hidden="true" , focusable="false"></div>
</div>
Status Bar
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⇧⌘P</span>
</div>
<div role="menuitemradio" aria-checked="true" class="group">
<div class="size-4 flex items-center justify-center">
<div class="size-2 rounded-full bg-foreground invisible group-aria-checked:visible" aria-hidden="true" , focusable="false"></div>
</div>
Activity Bar
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘B</span>
</div>
<div role="menuitemradio" aria-checked="false" class="group">
<div class="size-4 flex items-center justify-center">
<div class="size-2 rounded-full bg-foreground invisible group-aria-checked:visible" aria-hidden="true" , focusable="false"></div>
</div>
Panel
<span class="text-muted-foreground ml-auto text-xs tracking-widest">⌘S</span>
</div>
</div>
</div>
</div>
</div>
<script>
(() => {
const dropdownMenu = document.querySelector("#dropdown-menu-radio-group");
const radioButtons = dropdownMenu.querySelectorAll('div[role="menuitemradio"]');
radioButtons.forEach((radioButton) => {
radioButton.addEventListener("click", () => {
radioButtons.forEach((radioButton) => {
radioButton.setAttribute("aria-checked", "false");
});
radioButton.setAttribute("aria-checked", "true");
});
});
})();
</script>