Skip to content

BbSelectPopover

Provides popover-driven option selection and search.

import { BbSelectPopover } from 'bitboss-ui';

On this page

Props

NameTypeDefaultDescription
activatorRecord<string, any> | HTMLElement | null | undefined-

External activator element or component ref. When provided, the activator slot is not rendered and event listeners are attached programmatically to the referenced element.

adaptiveboolean | undefined`config.adaptive` (`true`)

When true, the panel opens as a bottom off-canvas sheet on mobile viewports instead of a floating popover; on desktop it stays a popover. The sheet reuses the same search field and options list, adding a representation of the current selection between them. Mirrors the adaptive behaviour of BbPopover/BbDropdown. When unset, falls back to the global config.adaptive.

Set it per instance only to disagree with the app-wide config.adaptive. Below the mobile breakpoint the placement props stop applying, because the panel is a sheet rather than a flyout.

See example
anchorRecord<string, any> | HTMLElement | null | undefinednull

Override the element used for popover positioning and width measurement. Useful when the focusable activator is narrower than its visual container (e.g. a button inside a wrapper). Falls back to activatorEl when omitted.

ariaDescribedbystring | string[] | undefined-

Space-separated IDREF(s) that describe this field for assistive technologies. Accepts either a string or array of ids.

ariaInvalidboolean | undefined-

Explicit invalid state for accessibility semantics. Falls back to hasErrors when not provided.

autocompletestring | undefined"off"

Browser autocomplete hint for the popover search input.

boundarystring | Record<string, any> | HTMLElement | null | undefined-

Constrains the popover to a specific boundary element.

clearableboolean | undefinedfalse

Shows a clear button inside the search field's icon stack: it takes the lens' place on hover/focus while there is a query or selection to clear, and resets both. Off by default. Leave it off when the component is wrapped by a control that already exposes its own clear affordance (e.g. BbSelect clears from the activator) to avoid a duplicate clear button.

Puts a small clear button inside the search field's icon stack — not on your trigger. For an explicit "empty this" most triggers want the clear callback from the activator or panel slot scope instead.

See example
compactboolean | undefinedfalse

Apply the compact density variant.

dependenciesunknown[] | undefined[]

Additional dependencies that trigger item reloading when changed.

Compared by value hash, not by reference. Pair it with enforceCoherence rather than with a watcher that clears the model on every upstream change.

See example
depsDebounceTimenumber | undefined0

Debounce delay (ms) for dependency-triggered reloads.

disabledboolean | undefinedfalse

Disables the component.

disableFlipboolean | undefinedfalse

Disables the automatic flip to the opposite side when the popover would overflow (flipping is on by default).

Inverts v2's flip, and the old name is silent: :flip="false" becomes disable-flip. The related hideArrow and arrowPadding are gone entirely — this panel is a frame and never draws an arrow.

See example
disableWritingboolean | "auto" | "mobile" | "desktop" | undefined'auto'

Controls whether the search input is shown inside the popover panel. true removes it everywhere; 'mobile' / 'desktop' remove it on that platform only; 'auto' removes it when an **array** items renders fewer options than the plugin's autoDisableWritingThreshold (default 6; disabled options count, group headers don't). 'auto' is decided when the panel opens and held until it closes, never applies to a provider (function) items, never when you listen to option:add (a creatable popover keeps its field), and is off when the threshold is 0. There is no global switch: hiding or forcing a field is this prop, per instance.

Removes the search field, and with it typing, filterBy and option:add. It inverts v2's allowWriting silently: :allow-writing="false" becomes disable-writing, allow-writing="not-mobile" becomes disable-writing="mobile".

See example
enforceCoherenceboolean | undefinedfalse

Reset modelValue to null or empty array if it no longer matches available items.

Opts each instance into a load of its own, because validating a seeded value is impossible without options. One filter costs one request; fifty provider-backed rows cost fifty. That is the reason a table hands every row the same array instead.

See example
filterBystring[] | undefined[]

Property paths used to filter options when the user types in the search input. When empty (default) options are matched against their display text. When non-empty the provided paths are searched instead of the display text. To disable filtering entirely set disableWriting so the user cannot type a query.

Replaces the match against the display text rather than adding to it, and every listed path must exist on every item or matching throws. With a provider it still applies on top of the response.

See example
groupBystring | ((item: Item) => string | number | symbol) | undefined-

Path to item property for grouping options.

hasErrorsboolean | undefinedfalse

Marks the component as being in error state.

hasWarningsboolean | undefinedfalse

Marks the component as being in a warning state. Visually mirrors hasErrors but uses --bb-warn chrome; errors take priority when both are set.

headerHeightnumber | undefined-

Height of group headers in the listbox (px). Defaults to 32px (24px compact; 36px / 32px on mobile viewports). Only applies when groupBy is set. An explicit value wins on every viewport.

idstring | undefined-

Explicit id for the component. Used to generate ids for listbox and options.

itemHeightnumber | undefined28

Height of the options in the listbox (px). Defaults to 28px (24px compact; 44px / 36px on mobile viewports). An explicit value wins on every viewport.

itemPropsBbSelectItemProps<Item> | undefined-

ADDITIONAL row fields resolved from each item — never its text or value (those stay on itemText / itemValue). Accepted fields: - description — a muted line under the option label. Setting it gives every row a fixed two-line height (the list is virtualized); the description stays on one line and ellipsizes. Announced as the option's description, never part of its name; matched by the search with the text when filterBy is empty. - prepend:icon — an icon before the label. - append:icon — an icon after the label; on the selected row the check takes its place. Either an object with a path or getter per field ({ description: 'email', 'prepend:icon': 'icon' }) or a function returning the fields ((item) => ({ description: item.email })). Without it no field renders — nothing is read off the item implicitly — and an empty value renders nothing for that item. Never shown in the trigger or the chips. To disable individual options use selectable.

itemsrequiredItem[] | ((query: string, prefill: boolean, modelValue: any) => Promise<Item[]>) | ((query: string, prefill: boolean, modelValue: any) => Item[])[]

Array of items or function to load them asynchronously. Functions receive (query, prefill, modelValue) and can return a promise. modelValue is the raw v-model: an array in multiple mode, the raw model value (possibly null) in single mode.

An array resolves on mount and costs nothing per instance — which is what makes one popover per table row viable. A function is per-instance, so reach for it only when the options genuinely depend on that instance.

See example
itemTextItemAccessor<Item, string> | undefined-

Path to item property for display text or function to extract it.

itemValueItemAccessor<Item> | undefined-

Path to item property for value or function to extract it.

loadingboolean | undefinedfalse

Display the loading state styles.

loadingTextstring | undefined-

Message shown while items are being loaded.

maxnumber | undefinedInfinity

Maximum number of selectable items (limits selection when multiple is true).

Applies to multiple only. At the cap the unselected options disable themselves while selected ones never do, so the reader can always trade one choice for another.

See example
modelValuerequiredany-

v-model value. Single value for single select, array for multiple select.

modelValueDebounceTimenumber | undefined0

Debounce delay (ms) for modelValue change handling.

multipleboolean | undefinedfalse

Enable multiple item selection. When true, modelValue must be an array.

The model must already be an array on the first render, and there is no chip row: summarising the selection on your trigger is part of owning it.

See example
noDataTextstring | undefined-

Message displayed when no items are available.

offCanvasPropsPartial<BbOffCanvasProps> | undefined-

Props for the phone sheet. A pt inside it, object or colon keys, styles only that sheet. Extra props forwarded to the BbOffCanvas sheet when adaptive is active on mobile (e.g. size, draggable, fullscreen, stack). A pt inside it styles ONLY the sheet, in BbOffCanvas's part words (root, header, title, description, content, footer, close; state open), object ({ pt: { header: '…' } }) or colon keys ({ 'pt:header': '…' }) alike. It merges after what this component forwards to its sheet, so it wins a conflict and drops nothing — see the passthrough guide § Inside the sheet.

offsetnumber | undefined4

Distance (px) between the activator and the popover.

paddingnumber | undefined10

Minimum page padding used by floating shift middleware.

placementPlacement | undefined"bottom"

Floating placement of the popover.

prefillboolean | "interaction" | undefined"interaction"

Controls when items are pre-loaded. 'interaction' loads on the first user interaction (opening the popover), true loads immediately on mount (and during SSR via onServerPrefetch), false is *search-first*: nothing is fetched until the user types a query — opening the panel alone does not load, and the panel shows a "search to begin" hint until it does. With the default, a seeded model resolves its option text on the first interaction, not on mount — set prefill: true when the trigger must show the selection's text without user interaction. Search-first needs a way to search and something to defer, so false falls back to loading on open when any of these is true: items is an array (nothing to defer — arrays resolve on mount), writing is disabled (no search field, so the user could never trigger a load), or the model is non-empty (its text must resolve to render the selection).

Unlike an in-field BbSelect, a standalone popover does **not** upgrade a seeded model to an eager load — a fetch per instance is something you ask for. Do not reach for true to populate a trigger label; resolve the label from your own state instead.

See example
ptPtItemMap<BbSelectPopoverPtPart, BbSelectPopoverOptionPtPart, BbSelectPopoverPtState, Item> | undefined-

Passthrough, object form: the same keys as the pt:* attributes without the prefix. See the pt:<part> row.

pt:clearPtValue | undefined-
pt:clear:disabledPtValue | undefined-
pt:clear:openPtValue | undefined-
pt:clear:selectedPtValue | undefined-
pt:footerPtValue | undefined-
pt:footer:disabledPtValue | undefined-
pt:footer:openPtValue | undefined-
pt:footer:selectedPtValue | undefined-
pt:headerPtValue | undefined-
pt:header:disabledPtValue | undefined-
pt:header:openPtValue | undefined-
pt:header:selectedPtValue | undefined-
pt:inputPtValue | undefined-
pt:input:disabledPtValue | undefined-
pt:input:openPtValue | undefined-
pt:input:selectedPtValue | undefined-
pt:item-descriptionPtValue | ((ctx: PtItemCtx<Item, BbSelectPopoverPtState>) => PtItemResult) | undefined-
pt:item-description:disabledPtValue | undefined-
pt:item-description:openPtValue | undefined-
pt:item-description:selectedPtValue | undefined-
pt:item-iconPtValue | ((ctx: PtItemCtx<Item, BbSelectPopoverPtState>) => PtItemResult) | undefined-
pt:item-icon:disabledPtValue | undefined-
pt:item-icon:openPtValue | undefined-
pt:item-icon:selectedPtValue | undefined-
pt:item-textPtValue | ((ctx: PtItemCtx<Item, BbSelectPopoverPtState>) => PtItemResult) | undefined-
pt:item-text:disabledPtValue | undefined-
pt:item-text:openPtValue | undefined-
pt:item-text:selectedPtValue | undefined-
pt:listPtValue | undefined-
pt:list:disabledPtValue | undefined-
pt:list:openPtValue | undefined-
pt:list:selectedPtValue | undefined-
pt:optionPtValue | ((ctx: PtItemCtx<Item, BbSelectPopoverPtState>) => PtItemResult) | undefined-
pt:option:disabledPtValue | undefined-
pt:option:openPtValue | undefined-
pt:option:selectedPtValue | undefined-
pt:panelPtValue | undefined-
pt:panel:disabledPtValue | undefined-
pt:panel:openPtValue | undefined-
pt:panel:selectedPtValue | undefined-
pt:rootPtValue | undefined-
pt:root:disabledPtValue | undefined-
pt:root:openPtValue | undefined-
pt:root:selectedPtValue | undefined-
pt:sheetPtValue | undefined-
pt:sheet:disabledPtValue | undefined-
pt:sheet:openPtValue | undefined-
pt:sheet:selectedPtValue | undefined-
queryDebounceTimenumber | undefined500

Debounce delay (ms) before triggering search queries after user input stops.

readonlyboolean | undefinedfalse

Prevents opening the popover while keeping the activator accessible.

searchInputAriaLabelstring | undefined`t('select.searchLabel')` (`'Search options'`)

Accessible label used by the search input rendered inside the popover. Falls back to the localized select.searchLabel key when omitted.

Names the in-panel search field, which defaults to the localized "Search options". It is not the trigger's name — that is the aria-label you put on the activator yourself.

See example
selectableboolean | ((item: Item) => boolean) | undefinedtrue

Defines whether options are selectable. Can be a global boolean that affects all options or a function that accepts an item and returns a boolean that only affects that item. Non-selectable options are rendered disabled.

stashboolean | undefinedfalse

Accumulate selected items across searches instead of replacing them.

Keeps picked options resolvable across searches. It does not help a trigger label, which should never depend on what the component has loaded.

See example
transitionDurationnumber | undefined250

Transition duration (ms) for popover show/hide animations.

widthstring | number | undefined"200"

Width of the popover in pixels, percentage, or 'auto'. A bare number is read as pixels — the default is the string '200', not the number 200.

Defaults to '200' (pixels), which is sized for a field-shaped picker. Pass a smaller number for a chip or table-cell trigger, and never 'auto': the panel is fixed-positioned, so auto resolves against the viewport and blows it out to the width of the page.

See example

v-model

Values kept in sync through v-model.

NameTypeDescription
modelValueany

Holds a single value, or an array under multiple. Give every instance its own model: several popovers bound to one ref each judge the same value against their own options, and every instance that disagrees emits null.

See example

Events

NameTypeDescription
active(e: "active"): void

Emitted when the component becomes active (focus enters it).

blur(e: "blur", event: FocusEvent): void

Emitted when the activator loses focus.

change(e: "change", event: Event): void

Emitted when the value changes via native change event.

click(e: "click", event: MouseEvent): void

Emitted when the activator is clicked.

focus(e: "focus", event: FocusEvent): void

Emitted when the activator receives focus.

hidden(e: "hidden"): void

Emitted once the options surface has finished closing.

hide(e: "hide"): void

Emitted as soon as the options surface starts to close.

inactive(e: "inactive"): void

Emitted when focus moves outside the entire component after it was active.

Validate or react on this, not on blur. Focus legitimately moves into the panel while it is open; inactive fires when focus has left both the trigger and the panel.

See example
input(e: "input", event: Event): void

Emitted on native input events from the search input.

option:add(e: "option:add", text: string): void

Emitted when the user types a new value and confirms it (Enter key) while writing is enabled (i.e. disableWriting does not suppress the search input for the current surface).

show(e: "show"): void

Emitted as soon as the options surface starts to open (the popover, or the sheet on a phone).

shown(e: "shown"): void

Emitted once the options surface is open and its open transition has finished.

update:modelValue(e: "update:modelValue", value: any): void

Emitted with the next selection value whenever selection changes.

Slots

The listed properties are the ones exposed to the slot scope.

NameTypeDescription
activatorBbSelectPopoverActivatorSlotProps

Replaces the default trigger element that opens the select popover.

Spread props onto the focusable element itself — the button inside a badge, not the badge around it. Label the trigger from your own state: text and selectedOptions resolve against loaded options, so with a provider they are empty until the panel first opens.

See example
footerBbSelectPopoverPanelSlotProps

Content pinned to the bottom of the panel, below the options list. Rendered in both the desktop popover and the adaptive sheet.

Renders below the options list in the desktop popover and the mobile sheet alike. With no field to hang a clear button on, this is usually where an explicit clear action belongs.

See example
groupBbSelectPopoverGroupSlotProps<Item>

Replaces the default group header rendered above grouped options.

headerBbSelectPopoverPanelSlotProps

Content pinned to the top of the panel, above the search field. Rendered in both the desktop popover and the adaptive sheet.

Renders above the search field. On a trigger with no label, a title here is often the only place the panel says what it is for.

See example
loading{ query: string; }

Content shown inside the options dropdown while items are loading.

no-data{ query: string; focus: () => void; }

Content shown when no options match the query or the list is empty.

optionBbSelectPopoverOptionSlotProps<Item>

Replaces the default rendering of each option row in the dropdown.

Changes from v2

Props removed or renamed in 3.0. If you are coming from v2, the answer is here.

  • allowWritingdisableWritingfails silently
  • arrowPaddingremovedfails silently
  • flipdisableFlipfails silently
  • hasWarninghasWarningsfails silently
  • hideArrowremovedfails silently
  • offcanvasPropsoffCanvasPropsfails silently
  • showArrowremovedfails silently