Skip to content

BbDropdown

An accessible action menu behind any trigger, including links, submenus and selectable groups.

import { BbDropdown } from 'bitboss-ui';

On this page

Use it for

Use BbDropdown to put several commands or navigation links behind one trigger: row actions, an account menu or a context menu. Describe rows with items; the component owns ARIA, focus and keyboard behaviour.

Use something else when

Also see: Options and items.

Passthrough

Hover or tap a part to outline it. The toggles turn on loading, errors and warnings where the component has them; only parts get a row.

Build a row actions menu

Spread the activator props onto a focusable trigger and pass the actions as data.

ORD-2026-0417 — no action yet

Use a real button and name icon-only triggers. Style the trigger inside #activator: a class on <BbDropdown> lands on the menu. There is no open state to bind; @show and @hide tell you when it opens and closes. The default slot is only for <BbDropdownGroup> companions: a trigger placed there opens nothing.

Choose the right item type

Use onClick for commands and href or to for navigation, so links keep native browser behaviour.

Q2 revenue review.pdf

2.4 MB · shared with the Finance team

label is what the row says and description adds a second line. A plain action or link needs no value; give one to any row something outside the menu reads, such as a selectable row or a row with its own slot. Gate destructive actions with useConfirm.

Coming from v2

Rows read label (v2 text) and value (v2 key); groups, new in v3, are named by id (key in early v3 betas). npx bitboss-ui check --fix and the ESLint rule rewrite literal rows. Destructive rows use variant: 'destructive'. In fetched groups, map extra row fields through itemProps; raw href, icons and disabled fields are otherwise ignored.

An account menu

Split rows into sections with nested arrays, pin the signed-in user in the header slot and put shortcut hints in item:append.

Signed in

Carry the shortcut in meta. The menu only displays it; binding the keys is up to you.

Use the directive for simple menus

When the trigger already exists and the menu is a flat items array, use the globally registered directive.

Team members

25 members

Use the component when you need slots, sections or a <BbDropdownGroup>.

Track async actions

Return a promise from an item's onClick: the activator slot's loading stays true until it settles, and repeat clicks on that item are ignored.

Catalogue

26 products · last synced at 09:24

Coming from v2

The exposed template-ref loading member is removed. Read the flag from the activator slot.

Give an item its own items array. Keep nesting shallow: the parent row opens the submenu and cannot also run an action or navigate.

Checkout fails on Safari after a coupon is applied

#482 · Backlog · Bug

Desktop uses flyouts; the mobile sheet drills down one level at a time. Submenus follow the ARIA menu keyboard model. ArrowRight, Enter, Space or a click open one with focus inside (its search box first, when it has one), and Escape closes one level and puts focus back on the row that opened it. A submenu that closes under the pointer hands focus to its row too. Under dir="rtl" submenus open to the left and the two inline arrows swap. On the sheet, Escape and the back control go back one level, focus on the row that opened it; at the top level Escape closes the sheet.

Build selectable groups

Mark a group selectable, then bind its state with <BbDropdownGroup> using the group's id. The model holds the picked row's value. A single group is a radio group: clicking the picked row again keeps it, so "Sort by" can never end up empty.

A pick in a single group closes the menu, wherever the group sits (in a submenu, beside other groups) and whatever else the menu holds; a multiple group stays open while you toggle. close-on-select overrides both: false keeps a settings menu of several radios open, true closes after every change.

Add clearable: true when "none" is a valid answer, as on "Channel": that click then empties the group and fires item:unselected. A row whose value is null is the other way to offer "none": while the model is null that row shows as checked, as in BbSelect and BbRadioGroup. Add multiple for checkbox semantics. Give the dropdown an id and useBbDropdownContext(id) reads its picks anywhere, as the chips above do; it is read-only, so change a pick through the group's v-model.

Rows that represent records

Carry application data in meta and target one group's rows with its named slot. Slot names are the row values in kebab-case: the row whose value is currentWorkspace is #current-workspace.

Working in Vantera Retail

Use #<groupId>:item:prepend for an avatar or logo. A slot replaces the icon on that edge; the selected check still wins on the trailing edge.

Search a long group

Mark a long group searchable and it gets its own search field that filters its rows. To fetch per query instead, pass a function as its items; it runs when the group is first shown.

ORD-2026-0418 · Refund not received

Assigned to Sofia Marchetti

The function receives one object, { query, reason, modelValue, signal }, the same as a BbSelect provider, so one fetcher serves both. A fetcher that matches the query itself takes filterBy: false on the group, so its answer is not filtered again. Set stash: true on a multiple group so earlier picks survive a new search, and prefill: false to load nothing until the user types.

groupBy on a group lists its rows under section headings and keeps it one group: one v-model, one search field. It takes a field path ('sector') or a function of the row, and reads the raw item on a fetched group. Rows with no section are listed first, as BbSelect's group-by does. Use separate group: true sections instead when each answers its own question.

Place and size the panel

Choose placement and width for the surrounding layout.

Sales report

August 2026 · 214 orders

width="100%" matches the trigger's width; a number keeps repeated menus aligned. Add compact for denser rows beside a compact select. For a context menu, pair trigger="contextMenu" with placement="cursor".

On a phone

On small screens the menu opens as a bottom sheet, with the same rows, slots and selection. Pass :adaptive="false" only when the menu must stay a flyout; off-canvas-props configures the sheet.

Coming from v2

arrowPadding is removed: dropdowns draw no arrow. The phone sheet is new and on by default. An item's data-* / aria-* keys no longer reach its row: move them to pt:item (Style items).

Style items

destructive is the only styled item variant. Register additional names through dropdownItemVariants, then style .bb-dropdown__item--variant-<name> yourself. Prefer pt:panel, pt:item and pt:input (every search field in the menu) for instance-level changes.

An item takes only the fields the menu reads. An attribute for its row, such as a data-* test hook or an aria-*, goes through pt:item, per row with the function form: :pt:item="({ item }) => ({ attrs: { 'data-test': item.value } })". An aria-label or aria-describedby set there names or describes the row.

Two custom properties on the menu size the desktop rows, with BbButton's names: --fs the label (default var(--bb-fs)) and --icon-size the leading and trailing icons (default 1rem). Submenus follow them; the phone sheet keeps its touch sizes.

vue
<BbDropdown class="[--fs:13px] [--icon-size:14px]" :items="items" />
Coming from v2

The floating wrapper is now a <div>, not a <span>. Class hooks remain; only element-specific selectors stop matching.