Skip to content

BbSelectPopover

Provides popover-driven option selection and search.

import { BbSelectPopover } from 'bitboss-ui';

On this page

Props

NameDescription
activator Type: Record<string, any> | HTMLElement | null

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

adaptive Type: booleanDefaultconfig.adaptive (true)

On mobile, the panel opens as a bottom sheet instead of a floating popover, with the same search and list. Unset, the global config.adaptive applies.

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
anchor Type: Record<string, any> | HTMLElement | nullDefaultnull

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.

ariaDescribedby Type: string | string[]

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

The popover has no message area. Render the error or warning next to your trigger and pass its id here: it lands on the trigger and on the search field.

See example
ariaInvalid Type: boolean

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

autocomplete Type: stringDefault"off"

Browser autocomplete hint for the popover search input.

boundary Type: string | Record<string, any> | HTMLElement | null

Constrains the popover to a specific boundary element: it stays inside it alongside its trigger (sideways for a popover above or below); away from the trigger it may pass the edge, and it flips at the viewport only.

clearable Type: booleanDefaultfalse

Shows a clear button in the search field, in the lens' place while there is a query or a selection; it resets both. None without a search field. Leave it off when the activator already clears (as BbSelect's does).

Puts a small clear button inside the search field's icon stack (not on your trigger), so a list without a search field gets none. For an explicit "empty this" most triggers want the clear callback from the activator or panel slot scope instead.

See example
compact Type: booleanDefaultfalse

Apply the compact density variant.

dependencies Type: unknown[]Default[]

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
depsDebounceTime Type: numberDefault0

Debounce delay (ms) for dependency-triggered reloads.

disabled Type: booleanDefaultfalse

Disables the component.

disableFlip Type: booleanDefaultfalse

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
disableWriting Type: boolean | "auto" | "mobile" | "desktop"Default'auto'

Hides the panel's search field: true everywhere, 'mobile' / 'desktop' on that platform only. 'auto' hides it, per open, for an array items shorter than the plugin's autoDisableWritingThreshold (6); never for a provider or with an option:add listener.

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
emptyText Type: string

Message displayed when no items are available.

enforceCoherence Type: booleanDefaultfalse

Drops the picks the items no longer contain (single: null). Checked after every applied, successful load whose reason is not 'search' (an empty one included), against the FETCHED rows only (a stash does not count); a search or failed load never prunes.

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
failedText Type: stringDefaultthe localized common.failedText ("Couldn't load items")

Message displayed in the empty state while the latest load FAILED (the provider threw): the twin of emptyText for that case.

filterBy Type: false | string[]Default[]

What typing filters on: empty = the display text and description; item paths = those fields (['name', 'email']); false = no filtering. Use false when the provider matched the query on the server, or its email/accent matches show as "No results".

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; false turns it off, for a provider that matches on the server.

See example
groupBy Type: string | ((item: Item) => string | number | symbol)

Path to item property for grouping options.

hasErrors Type: booleanDefaultfalse

Marks the component as being in error state.

Paints the error icon in the search field and sets aria-invalid on the trigger. It shows no message: that is yours, wired through ariaDescribedby.

See example
hasWarnings Type: booleanDefaultfalse

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

headerHeight Type: number

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.

id Type: string

Seeds the ids the popover sets: its listbox is <id>_listbox (the activator's aria-controls) and each option's id derives from it. Generated when omitted.

itemHeight Type: numberDefault28

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.

itemProps Type: BbSelectItemProps<Item>

ADDITIONAL row fields, never the text or value: description (a muted line under the label), prepend:icon, append:icon. An object of paths or getters per field, or a function of the item. Nothing is read implicitly; none shows in the trigger or the chips.

items requiredType: Item[] | ((context: BbSelectPopoverItemsContext) => Item[] | Promise<Item[]>)Default[]

The options: an array, or a sync or async provider given a BbSelectPopoverItemsContext (query, reason, modelValue, signal). Branch on reason !== 'search' to resolve the selection by id. A provider fails by THROWING, never by returning [].

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
itemText Type: ItemAccessor<Item, string>Defaultown label, else text, else JSON.stringify(item)

Path to item property for display text or function to extract it. Unset: own label, else text ({ label, value } needs no accessor).

itemValue Type: ItemAccessor<Item>Defaultown value, else the whole item

Path to item property for value or function to extract it. Unset: own value, even null.

knownItems Type: ([Known] extends [undefined] ? BbSelectKnownItems : Known)

{ value, label } rows naming the value the app already holds, whatever the options' shape: the activator's text with no request until the first open (prefill: true still loads). Not options: accessors never run on them; slots get one as item, known: true, until the list brings its own.

loading Type: booleanDefaultfalse

Display the loading state styles.

loadingText Type: string

Message shown while items are being loaded.

max Type: numberDefaultInfinity

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
modelValue requiredType: any

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

modelValueDebounceTime Type: numberDefault0

Debounce delay (ms) for modelValue change handling.

multiple Type: booleanDefaultfalse

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
offCanvasProps Type: Partial<BbOffCanvasProps>

Props for the phone sheet (the BbOffCanvas that adaptive renders on mobile), spread over its defaults. A pt inside it styles only the sheet (passthrough guide § Inside the sheet).

offset Type: numberDefault4

Distance (px) between the activator and the popover.

padding Type: numberDefault10

Minimum page padding used by floating shift middleware.

placement Type: PlacementDefault"bottom"

Floating placement of the popover.

prefill Type: boolean | "interaction"Default"interaction"

When the options first load: 'interaction' on first open, true on mount (and in SSR), false once the user types (on open with disable-writing or a non-empty model). An array items always resolves on mount; else a seeded model's text waits for the first open.

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
pt Type: PtItemMap<BbSelectPopoverPtPart, BbSelectPopoverOptionPtPart, BbSelectPopoverPtState, Item, never, BbSelectSlotRow<Item, Known>>

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

pt:<part> Type: PtValue | ((ctx: PtItemCtx<Item, BbSelectPopoverPtState, never, BbSelectSlotRow<Item, Known>>) => PtItemResult)

Passthrough, attribute form: one attribute per part, pt:<part>, and per part and state, pt:<part>:<state>. Parts: clear, footer, header, input, itemDescription, itemIcon, itemText, list, option, panel, root, sheet. States: disabled, open, selected.

See example
queryDebounceTime Type: numberDefault500

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

readonly Type: booleanDefaultfalse

Prevents opening the popover while keeping the activator accessible.

searchInputAriaLabel Type: stringDefaultt('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
selectable Type: boolean | ((item: Item) => boolean)Defaulttrue

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.

stash Type: booleanDefaultfalse

Keeps picked options named across searches: a pick stays resolvable when a later search or a failed load no longer returns it. It follows the model, and does not keep a pick valid: enforce-coherence still prunes it.

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
transitionDuration Type: numberDefault250

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

width Type: string | numberDefault"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.

NameDescription
modelValue Type: any

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

NameDescription
active Type: (): void

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

blur Type: (event: FocusEvent): void

Emitted when the activator loses focus.

change Type: (event: Event): void

Emitted when the value changes via native change event.

click Type: (event: MouseEvent): void

Emitted when the activator is clicked.

focus Type: (event: FocusEvent): void

Emitted when the activator receives focus.

hidden Type: (): void

Emitted once the options surface has finished closing.

hide Type: (): void

Emitted as soon as the options surface starts to close.

inactive Type: (): void

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

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 Type: (event: Event): void

Emitted on native input events from the search input.

option:add Type: (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 Type: (): void

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

shown Type: (): void

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

update:modelValue Type: (value: any): void

Emitted with the next selection value whenever selection changes.

Slots

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

NameDescription
activator Type: BbSelectPopoverActivatorSlotProps

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
empty Type: { query: string; focus: () => void; error: unknown; }

Content shown when no options match the query or the list is empty. error is what the latest failed load threw, untouched (null when none).

group Type: BbSelectPopoverGroupSlotProps<Item>

Replaces the default group header rendered above grouped options. Not called for rows with no group (group-by gives null/undefined, known rows): they are listed without a header.

header Type: BbSelectPopoverPanelSlotProps

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 Type: { query: string; }

Content shown inside the options dropdown while items are loading.

option Type: BbSelectPopoverOptionSlotProps<Item, Known>

Replaces the default rendering of each option row in the dropdown. With known-items bound, a row the list has not brought yet is the known { value, label } row, known: true: check known before reading an option field off item.

Renamed and removed

Names from v2 and from earlier 3.0 betas that no longer work, and what to write instead.

  • allowWritingdisableWritingfails silently
  • arrowPaddingremovedfails silently
  • flipdisableFlipfails silently
  • hasWarninghasWarningsfails silently
  • hideArrowremovedfails silently
  • slotno-dataemptyfails silently
  • noDataTextemptyTextfails silently
  • showArrowremovedfails silently