Skip to content

BbSelect

Selects one or many options from structured data.

import { BbSelect } from 'bitboss-ui';

On this page

Props

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

When true, the options panel opens as a bottom off-canvas sheet on mobile viewports instead of a floating popover; on desktop it stays a popover. When unset, falls back to the global config.adaptive.

Set it per instance only to disagree with the app-wide config.adaptive. If every select in your app should open as a sheet on mobile, set it once in the plugin config instead.

See example
append:iconstring | undefinedthe chevron-down glyph

Name of the icon to be added at the end of the input.

Replaces the chevron rather than sitting next to it. v3 removed both the showChevron prop and the #chevron slot; this is what customises the trailing icon.

See example
autocompletestring | undefined-

Guides to the browser as to the type of information expected in the field.

autofocusBooleanish | undefined-

Sets autofocus on page load.

clearableboolean | undefinedfalse

Displays a clear button when the input has a value and is being interacted with.

commaboolean | undefinedfalse

Sets the display model for selected values to a list of comma separated string. In this mode the user cannot deselect an option by pressing the close button.

A display mode, and it costs the per-value close button: in comma mode a value can only be removed from the panel, and clearable — which empties everything — is the only button left on the field.

See example
compactboolean | undefinedfalse

Sets the component in a compact mode.

dependenciesunknown[] | undefined-

Defines an array of dependencies that will trigger actions in the component upon change.

Compared by value hash, not by reference, so an array rebuilt on every render does not refetch. Coherence is lazy: a select with a provider and an empty model ignores changes here until its first load.

See example
depsDebounceTimenumber | undefined0

Timeout used to debounce response to changes to dependencies.

Defaults to 0. Raise it when the upstream field changes on every keystroke.

See example
descriptionstring | undefined-

Descriptive text displayed below the label and above the input. Unlike the hint it is always visible, and it is linked to the input via aria-describedby (after any errors / warnings, before the hint).

directionInputDirection | undefined-

Direction of the layout of the component. Can either be a predefined value or a pattern separated by a space like xx xxxxx.

disabledboolean | undefinedfalse

Disables the component.

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

Controls whether the search input is shown inside the dropdown 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 select 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.

Also the supported way to switch filtering off, and it takes option:add with it — both need a query to exist. It inverts v2's allowWriting, and the old name lands in $attrs silently: :allow-writing="false" becomes disable-writing, allow-writing="not-mobile" becomes disable-writing="mobile".

See example
enforceCoherenceboolean | undefinedfalse

If coherence is enforced the input cannot have a modelValue that is incoherent with its current items. modelValue will be reset upon incoherence.

Reach for it when items can change under a value that is already selected — a dependent select, or a list reloaded from the server. Without it a stale selection survives and gets submitted.

See example
errorsstring | string[] | undefined-

Can be a string or an array of string containing the messages to display. They render in an aria-live="polite" region (announced when they appear) and, while the list is non-empty, are referenced FIRST from the control's aria-describedby, so the reason the field is invalid is re-read whenever the control regains focus.

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 — a server match whose text does not contain the query is hidden by this local pass.

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

Path to item property for grouping options.

Grouping changes how the list virtualises: a flat list windows per row, a grouped list windows per group, so one group with thousands of options mounts in full. Keep such lists flat, or chunk them.

See example
hasErrorsboolean | undefinedfalse

Define if the component should be in an error state. It usually attaches a CSS class for styling purposes.

hasWarningsboolean | undefinedfalse

Define if the component should be in a warning state. Same chrome as errors with --bb-warn; errors take priority.

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.

Defaults to 32px (24px compact, 36px in the mobile sheet). Set it in the prop and not in CSS — the number also feeds the virtualiser, so a stylesheet override desynchronises the paint from the scroll maths.

See example
hideLabelboolean | undefinedfalse

Visually hides the label of the input while maintaining accessibility.

hintstring | undefined-

Text box to be displayed near the input, usually to indicate instructions.

idstring | undefined-

The identifier of the component.

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.

Defaults to 28px (24px compact, 44px in the mobile sheet). Raise it to match taller #option markup; a CSS override moves the paint without moving the scroll maths.

See example
itemPropsBbSelectItemProps<T> | 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.

itemsrequiredT[] | ((query: string, prefill: boolean, modelValue: any) => Promise<T[]>) | ((query: string, prefill: boolean, modelValue: any) => T[])-

Used to retrieve items; can be an array or a function.

Pass your domain objects as they are and describe them with item-text and item-value. Pass a function when the list is too large to ship up front; it receives the current query and must resolve to the options for it.

See example
itemTextItemAccessor<T, string> | undefined-

Defines a path that returns a property of the object to use as text or a function that returns a string.

itemValueItemAccessor<T> | undefined-

Defines a path that returns a property of the object to use as value or a function that returns any value.

Prefer a primitive id: stable, unique, cheap to submit. Omit it and the whole object becomes the value; matching still uses its value hash, but the model then carries the full object.

See example
labelrequiredstring-

Text content of the label of the element.

labelMode"outside" | "floating" | "inside" | undefined-

Label rendering mode.

labelPosition"left" | "center" | "right" | undefined-

Sets the text alignment of the label.

loadingboolean | undefinedfalse

Sets the component in a loading state, usually triggering some visual styles.

External pending state only — a parent form saving, say. Fetching through a provider drives its own loading state, so mirroring it here does nothing but hide bugs.

See example
loadingTextstring | undefined-

String displayed while items are being loaded.

maxnumber | undefined-

Maximum number of selectable items when the multiple flag is set.

Applies to multiple only, and defaults to unlimited. At the cap the unselected options disable themselves while the selected ones never do, so a reader who reached the limit can always trade one choice for another.

See example
maxSelectedLabelsnumber | undefinedInfinity

Maximum number of labels shown before collapsing to a count summary. Unset, the labels collapse only when they overflow the field.

Applies to multiple only. Past the limit the extra selections collapse into a "+N" summary, which is not rendered through the chip slot.

See example
modelValuerequiredany-

Used by v-model. Can be any serializable type.

modelValueDebounceTimenumber | undefined0

Timeout used to debounce response to changes to modelValue.

multipleboolean | undefinedfalse

Allows the selection of multiple items.

The model must already be an array on the first render. A scalar or an unseeded ref() throws at setup rather than guessing.

See example
namestring | undefined-

Defines the name of the input.

Renders one hidden input per value, JSON-stringifying anything that is not a string. In v3 an empty selection posts no field at all, like a native <select multiple> with nothing chosen; v2 posted the literal string "null".

See example
noDataTextstring | undefined-

String displayed when there are no items to display.

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. 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.

persistentHintboolean | undefinedfalse

Keeps the hint displayed.

placeholderstring | undefined-

String displayed when there's no data.

Single mode only. An empty multiple select renders nothing, so put its guidance in description or hint.

See example
prefillboolean | "interaction" | undefined'interaction'

Controls when items are pre-loaded. 'interaction' loads on the first user interaction, true loads immediately on mount, false is *search-first*: with a provider, nothing is fetched until the user types — opening alone does not load, and the panel shows a "search to begin" hint meanwhile. false falls back to loading on open when the user could not otherwise reach the options: array items (resolved on mount regardless), or disable-writing (no search field to type into). When the input has a value, it always loads immediately so the selection's text can render.

Defaults to 'interaction'. false is search-first — nothing loads until the user types — which is a change of meaning from v2, where it meant "load on first open"; that is now the default. Array items and a non-empty model both load regardless, and a null "All" option counts as empty, so it needs prefill: true to resolve.

See example
prepend:iconstring | undefined-

Name of the icon to be added at the start of the input.

ptPtItemMap<BbSelectPtPart, BbSelectOptionPtPart, BbSelectPtState, T> | undefined-

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

pt:boxPtValue | undefined-
pt:box:disabledPtValue | undefined-
pt:box:errorsPtValue | undefined-
pt:box:has-valuePtValue | undefined-
pt:box:loadingPtValue | undefined-
pt:box:openPtValue | undefined-
pt:box:readonlyPtValue | undefined-
pt:box:selectedPtValue | undefined-
pt:box:warningsPtValue | undefined-
pt:clearPtValue | undefined-
pt:clear:disabledPtValue | undefined-
pt:clear:errorsPtValue | undefined-
pt:clear:has-valuePtValue | undefined-
pt:clear:loadingPtValue | undefined-
pt:clear:openPtValue | undefined-
pt:clear:readonlyPtValue | undefined-
pt:clear:selectedPtValue | undefined-
pt:clear:warningsPtValue | undefined-
pt:descriptionPtValue | undefined-
pt:description:disabledPtValue | undefined-
pt:description:errorsPtValue | undefined-
pt:description:has-valuePtValue | undefined-
pt:description:loadingPtValue | undefined-
pt:description:openPtValue | undefined-
pt:description:readonlyPtValue | undefined-
pt:description:selectedPtValue | undefined-
pt:description:warningsPtValue | undefined-
pt:hintPtValue | undefined-
pt:hint:disabledPtValue | undefined-
pt:hint:errorsPtValue | undefined-
pt:hint:has-valuePtValue | undefined-
pt:hint:loadingPtValue | undefined-
pt:hint:openPtValue | undefined-
pt:hint:readonlyPtValue | undefined-
pt:hint:selectedPtValue | undefined-
pt:hint:warningsPtValue | undefined-
pt:iconPtValue | undefined-
pt:icon:disabledPtValue | undefined-
pt:icon:errorsPtValue | undefined-
pt:icon:has-valuePtValue | undefined-
pt:icon:loadingPtValue | undefined-
pt:icon:openPtValue | undefined-
pt:icon:readonlyPtValue | undefined-
pt:icon:selectedPtValue | undefined-
pt:icon:warningsPtValue | undefined-
pt:inputPtValue | undefined-
pt:input:disabledPtValue | undefined-
pt:input:errorsPtValue | undefined-
pt:input:has-valuePtValue | undefined-
pt:input:loadingPtValue | undefined-
pt:input:openPtValue | undefined-
pt:input:readonlyPtValue | undefined-
pt:input:selectedPtValue | undefined-
pt:input:warningsPtValue | undefined-
pt:item-descriptionPtValue | ((ctx: PtItemCtx<T, BbSelectPtState>) => PtItemResult) | undefined-
pt:item-description:disabledPtValue | undefined-
pt:item-description:errorsPtValue | undefined-
pt:item-description:has-valuePtValue | undefined-
pt:item-description:loadingPtValue | undefined-
pt:item-description:openPtValue | undefined-
pt:item-description:readonlyPtValue | undefined-
pt:item-description:selectedPtValue | undefined-
pt:item-description:warningsPtValue | undefined-
pt:item-iconPtValue | ((ctx: PtItemCtx<T, BbSelectPtState>) => PtItemResult) | undefined-
pt:item-icon:disabledPtValue | undefined-
pt:item-icon:errorsPtValue | undefined-
pt:item-icon:has-valuePtValue | undefined-
pt:item-icon:loadingPtValue | undefined-
pt:item-icon:openPtValue | undefined-
pt:item-icon:readonlyPtValue | undefined-
pt:item-icon:selectedPtValue | undefined-
pt:item-icon:warningsPtValue | undefined-
pt:item-textPtValue | ((ctx: PtItemCtx<T, BbSelectPtState>) => PtItemResult) | undefined-
pt:item-text:disabledPtValue | undefined-
pt:item-text:errorsPtValue | undefined-
pt:item-text:has-valuePtValue | undefined-
pt:item-text:loadingPtValue | undefined-
pt:item-text:openPtValue | undefined-
pt:item-text:readonlyPtValue | undefined-
pt:item-text:selectedPtValue | undefined-
pt:item-text:warningsPtValue | undefined-
pt:labelPtValue | undefined-
pt:label:disabledPtValue | undefined-
pt:label:errorsPtValue | undefined-
pt:label:has-valuePtValue | undefined-
pt:label:loadingPtValue | undefined-
pt:label:openPtValue | undefined-
pt:label:readonlyPtValue | undefined-
pt:label:selectedPtValue | undefined-
pt:label:warningsPtValue | undefined-
pt:listPtValue | undefined-
pt:list:disabledPtValue | undefined-
pt:list:errorsPtValue | undefined-
pt:list:has-valuePtValue | undefined-
pt:list:loadingPtValue | undefined-
pt:list:openPtValue | undefined-
pt:list:readonlyPtValue | undefined-
pt:list:selectedPtValue | undefined-
pt:list:warningsPtValue | undefined-
pt:messagePtValue | undefined-
pt:message:disabledPtValue | undefined-
pt:message:errorsPtValue | undefined-
pt:message:has-valuePtValue | undefined-
pt:message:loadingPtValue | undefined-
pt:message:openPtValue | undefined-
pt:message:readonlyPtValue | undefined-
pt:message:selectedPtValue | undefined-
pt:message:warningsPtValue | undefined-
pt:optionPtValue | ((ctx: PtItemCtx<T, BbSelectPtState>) => PtItemResult) | undefined-
pt:option:disabledPtValue | undefined-
pt:option:errorsPtValue | undefined-
pt:option:has-valuePtValue | undefined-
pt:option:loadingPtValue | undefined-
pt:option:openPtValue | undefined-
pt:option:readonlyPtValue | undefined-
pt:option:selectedPtValue | undefined-
pt:option:warningsPtValue | undefined-
pt:panelPtValue | undefined-
pt:panel:disabledPtValue | undefined-
pt:panel:errorsPtValue | undefined-
pt:panel:has-valuePtValue | undefined-
pt:panel:loadingPtValue | undefined-
pt:panel:openPtValue | undefined-
pt:panel:readonlyPtValue | undefined-
pt:panel:selectedPtValue | undefined-
pt:panel:warningsPtValue | undefined-
pt:prefixPtValue | undefined-
pt:prefix:disabledPtValue | undefined-
pt:prefix:errorsPtValue | undefined-
pt:prefix:has-valuePtValue | undefined-
pt:prefix:loadingPtValue | undefined-
pt:prefix:openPtValue | undefined-
pt:prefix:readonlyPtValue | undefined-
pt:prefix:selectedPtValue | undefined-
pt:prefix:warningsPtValue | undefined-
pt:rootPtValue | undefined-
pt:root:disabledPtValue | undefined-
pt:root:errorsPtValue | undefined-
pt:root:has-valuePtValue | undefined-
pt:root:loadingPtValue | undefined-
pt:root:openPtValue | undefined-
pt:root:readonlyPtValue | undefined-
pt:root:selectedPtValue | undefined-
pt:root:warningsPtValue | undefined-
pt:sheetPtValue | undefined-
pt:sheet:disabledPtValue | undefined-
pt:sheet:errorsPtValue | undefined-
pt:sheet:has-valuePtValue | undefined-
pt:sheet:loadingPtValue | undefined-
pt:sheet:openPtValue | undefined-
pt:sheet:readonlyPtValue | undefined-
pt:sheet:selectedPtValue | undefined-
pt:sheet:warningsPtValue | undefined-
pt:spinnerPtValue | undefined-
pt:spinner:disabledPtValue | undefined-
pt:spinner:errorsPtValue | undefined-
pt:spinner:has-valuePtValue | undefined-
pt:spinner:loadingPtValue | undefined-
pt:spinner:openPtValue | undefined-
pt:spinner:readonlyPtValue | undefined-
pt:spinner:selectedPtValue | undefined-
pt:spinner:warningsPtValue | undefined-
pt:suffixPtValue | undefined-
pt:suffix:disabledPtValue | undefined-
pt:suffix:errorsPtValue | undefined-
pt:suffix:has-valuePtValue | undefined-
pt:suffix:loadingPtValue | undefined-
pt:suffix:openPtValue | undefined-
pt:suffix:readonlyPtValue | undefined-
pt:suffix:selectedPtValue | undefined-
pt:suffix:warningsPtValue | undefined-
queryDebounceTimenumber | undefined500

Time to wait when the user stops writing in the input before querying data.

Defaults to 500ms. The component owns the debounce; a hand-rolled one around the provider fights it.

See example
readonlyboolean | undefinedfalse

Sets the input in a readonly state.

requiredboolean | undefinedfalse

Sets the input as required.

Marks the field and feeds the asterisk, but does not trigger native constraint validation: the hidden inputs carrying the value are barred from it by the HTML spec. The message a reader sees still comes from errors.

See example
reverseboolean | undefinedfalse

Reverses the layout. Applicable in every direction the order of the label and the input is swapped.

selectableboolean | ((item: T) => 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.

The only supported way to disable individual options. A disabled field on the item object is inert domain data in v3 — feed it to the predicate instead, or filter the row out of items.

See example
selectedLabelsFn((count: number) => string) | undefined-

Function that returns a string to be displayed when the number of selected labels is greater than maxSelectedLabels.

stashboolean | undefinedfalse

Adds a stash to accumulate selected values across searches.

Keeps every previously fetched option resolvable, which is what stops a chip losing its text after a later search. The flip side: on a select with dependencies it also keeps stale picks coherent, so enforceCoherence will never prune them.

See example
transitionDurationnumber | undefined250

How long the transition has to last in milliseconds.

variantInputVariantType | undefined'outline'

Visual variant of the field box — the same names and tokens as the BbButton variants. Colours only: height, padding and border width are identical across variants, so a form never reflows when one changes. 'ghost' has no border in any state (errors and warnings show through the icon and the messages); every variant keeps the focus ring. Register extra names with the plugin's inputVariants option.

warningsstring | string[] | undefined-

Warning messages to display beneath the field, in their own aria-live region and amber (--bb-text-warn). A string or array; a non-empty list implies the warning state. Errors suppress warnings when both are set. While displayed they are also referenced from the control's aria-describedby, after any errors and before the description/hint.

v-model

Values kept in sync through v-model.

NameTypeDescription
modelValueany

Holds a single value, or an array of them under multiple. Its shape follows item-value: ids when you set one, whole objects when you do not.

See example

Events

NameTypeDescription
active(e: "active"): void
blur(e: "blur", event: FocusEvent): void
change(e: "change", event: Event): void
click(e: "click", event: MouseEvent): void
focus(e: "focus", event: FocusEvent): void
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

Validate on this, not on blur. Focus legitimately moves into the panel while the reader is choosing, so blur fires mid-interaction; inactive fires when focus has left both the control and the panel.

See example
input(e: "input", event: Event): void
option:add(e: "option:add", text: string): void

Fires when the user commits an option the list did not contain. Use it to create the record on your side, then push it into items so the selection resolves. It can fire with an empty string, and disableWriting removes it along with the search field.

See example
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

Slots

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

NameTypeDescription
append{ query: string; focus: () => void; close: () => Promise<void>; }

Content appended inside the input field, after the value area.

append-outer{ query: string; close: () => Promise<void>; }

Content appended outside the input field wrapper.

chipBbSelectChipSlotProps<T>

Replaces the default badge rendered for each selection in multiple mode. Not called in comma mode, nor for the collapsed "+N" summary.

Replaces the badge for each selection in multiple mode. It is not called in comma mode, nor for the collapsed "+N" summary, so keep the two presentations recognisably the same.

See example
descriptionBbSelectDescriptionSlotProps

Replaces the description text. Provided alone (no description prop) it still renders the description region and wires it into aria-describedby.

footerBbSelectPanelSlotProps

Content pinned to the bottom of the options 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 in the mobile sheet alike. Select-all lives here: the model is yours, so assigning every value to it is the whole implementation.

See example
groupBbSelectGroupSlotProps<T>

Replaces the default group header rendered above grouped options.

Styles the group header only. length is the size of the group and index the list position of its first option.

See example
headerBbSelectPanelSlotProps

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

Renders above the search field. Derive any summary from your own model rather than from selectedOptions, which resolves against loaded options and is empty until a provider first runs.

See example
labelBbSelectLabelSlotProps

Replaces the default label text rendered above the input.

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.

optionBbSelectOptionSlotProps<T>

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

Replaces the row's text, not the selection check — that renders outside the slot and survives any markup. Taller rows need itemHeight to match.

See example
prefixobject

Content rendered as an inline prefix inside the input field.

prepend{ query: string; focus: () => void; close: () => Promise<void>; }

Content prepended inside the input field, before the value area.

prepend-outer{ query: string; close: () => Promise<void>; }

Content prepended outside the input field wrapper.

suffixobject

Content rendered as an inline suffix inside the input field.

Changes from v2

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

  • allowWritingdisableWritingfails silently
  • hasWarninghasWarningsfails silently
  • offcanvasPropsoffCanvasPropsfails silently