Skip to content

Dropdown Menu

.bp-dropdown is a custom element (<bp-dropdown>) that wires keyboard navigation, focus management, and open/close state. CSS handles all visual appearance via [data-open]. The panel is a <ul> of <button> or <a> items.

Basic dropdown

<bp-dropdown class="bp-dropdown">
<button class="bp-btn bp-btn--outline bp-dropdown__trigger">
Options ▾
</button>
<ul class="bp-dropdown__panel" role="menu">
<li><button class="bp-dropdown__item" role="menuitem">Edit</button></li>
<li><button class="bp-dropdown__item" role="menuitem">Duplicate</button></li>
<li><div class="bp-dropdown__separator" role="separator"></div></li>
<li><button class="bp-dropdown__item bp-dropdown__item--danger" role="menuitem">Delete</button></li>
</ul>
</bp-dropdown>

With icons

<bp-dropdown class="bp-dropdown">
<button class="bp-btn bp-btn--primary bp-dropdown__trigger">
Actions ▾
</button>
<ul class="bp-dropdown__panel" role="menu">
<li>
<a href="#" class="bp-dropdown__item" role="menuitem">
<svg class="bp-dropdown__icon" viewBox="0 0 16 16" fill="currentColor">
<path d="M11.013 1.427a1.75 1.75 0 0 1 2.474 0l1.086 1.086a1.75 1.75 0 0 1 0 2.474l-8.61 8.61c-.21.21-.47.364-.756.445l-3.251.93a.75.75 0 0 1-.927-.928l.929-3.25c.081-.286.235-.547.445-.758l8.61-8.61Z"/>
</svg>
Edit
</a>
</li>
<li>
<button class="bp-dropdown__item" role="menuitem">
<svg class="bp-dropdown__icon" viewBox="0 0 16 16" fill="currentColor">
<path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"/>
<path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"/>
</svg>
Duplicate
</button>
</li>
<li><div class="bp-dropdown__separator" role="separator"></div></li>
<li>
<button class="bp-dropdown__item bp-dropdown__item--danger" role="menuitem">
<svg class="bp-dropdown__icon" viewBox="0 0 16 16" fill="currentColor">
<path d="M11 1.75V3h2.25a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1 0-1.5H5V1.75C5 .784 5.784 0 6.75 0h2.5C10.216 0 11 .784 11 1.75ZM4.496 6.675l.66 6.6a.25.25 0 0 0 .249.225h5.19a.25.25 0 0 0 .249-.225l.66-6.6a.75.75 0 0 1 1.492.149l-.66 6.6A1.748 1.748 0 0 1 10.595 15h-5.19a1.75 1.75 0 0 1-1.741-1.575l-.66-6.6a.75.75 0 1 1 1.492-.15Z"/>
</svg>
Delete
</button>
</li>
</ul>
</bp-dropdown>

The <bp-dropdown> custom element is required for keyboard navigation. Load it once:

<script type="module" src="https://unpkg.com/@be-partner-labs/ds/js/bp-dropdown.esm.min.js"></script>

Or via npm:

import '@be-partner-labs/ds/js/bp-dropdown'

What JS handles:

  • Opens/closes panel on trigger click, sets [data-open]
  • Arrow Up/Down moves focus between items
  • Home/End jumps to first/last item
  • Escape closes and returns focus to trigger
  • Click outside closes the panel

What CSS handles: All visual appearance via [data-open] on the wrapper.

VariableDefaultDescription
--dropdown-bg--bp-color-bg-elevatedPanel background
--dropdown-border1px solid --bp-color-borderPanel border
--dropdown-radius--bp-radius-lgPanel border radius
--dropdown-shadow--bp-shadow-lgPanel shadow
--dropdown-min-width12remMinimum panel width
--dropdown-item-gap--bp-space-2 --bp-space-3Item padding
No axe violations tested 2026-05-11
  • Use <ul role="menu"> for the panel and role="menuitem" on each item.
  • The trigger gets aria-haspopup="true" and aria-expanded wired automatically.
  • Keyboard pattern follows ARIA Menu Button: Arrow keys navigate items, Escape closes, Tab closes without returning focus.
  • Disabled items: use aria-disabled="true" on .bp-dropdown__item — they are skipped in keyboard navigation.
  • For navigation links (not actions), use <a> items instead of <button> — the keyboard behavior is the same.
APIAvailabilityUsed forWithout it
Custom Elements Widely available Baseline 2020 <bp-dropdown> keyboard wiringMenu opens/closes only on click, no keyboard nav
visibility + opacity transition Widely available Baseline 2012 Smooth open/closeInstant show/hide

State lives entirely in [data-open] on <bp-dropdown>. The custom element sets and removes this attribute; CSS reads it. Keyboard navigation uses a roving focus pattern — focus() is called directly on items, no tabindex changes needed since items are naturally focusable <button> or <a> elements.