Skip to content

BbSelect

Selects one or many options from structured data.

import { BbSelect } from 'bitboss-ui';

On this page

Props

NameDescription
adaptive Type: booleanDefaultconfig.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:icon Type: stringDefaultthe 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
autocomplete Type: string

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

autofocus Type: Booleanish

Focuses the field when it mounts (a dialog already open included), unless the user is typing in another field.

clearable Type: booleanDefaultfalse

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

comma Type: booleanDefaultfalse

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
compact Type: booleanDefaultfalse

Compact density: the field uses the -compact twins of --bb-input-h, --bb-input-py and --bb-label-spacing-y, so a --bb-input-h set on the component is ignored while it is on. Size a compact field with --bb-input-h-compact (and --bb-input-py-compact).

dependencies Type: unknown[]

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

Timeout used to debounce response to changes to dependencies.

Raise it when the upstream field changes on every keystroke.

See example
description Type: string

Text between the label and the input. Unlike the hint it is always visible; it joins the input's aria-describedby after any errors / warnings, before the hint.

disabled Type: booleanDefaultfalse

Disables the component.

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.

Unset, the search field hides itself on an array of fewer than six options (the plugin's autoDisableWritingThreshold); a provider and a select that listens to option:add always keep it. true is 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
emptyText Type: string

String displayed when there are no items to display.

The line the panel shows when nothing matches. Use the empty slot instead when that state needs an action, such as creating the missing value.

See example
enforceCoherence Type: booleanDefaultfalse

After every successful load that is not a search, removes the picks the fetched rows do not contain (a single model resets to null). A stash does not count, known-items vouch until a parent change; a search or a failed load never prunes.

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
errors Type: string | string[]

Error messages under the field, a string or array, in an aria-live="polite" region and first in the control's aria-describedby, so they are re-read when it regains focus.

failedText Type: stringDefaultthe localized common.failedText ("Couldn't load items")

String 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: a server match whose text does not contain the query is hidden by this local pass. false turns the local pass off, for a provider that matches on the server. A wrapper that redeclares this prop must default it to [], or Vue's Boolean cast forwards false.

See example
formless Type: booleanDefaultfalse

Keep this control out of the enclosing BbForm entirely: not submitted, not dirty, not reset, not validated. For a control that is not form data (a filter, a "show advanced" switch).

For a control that is not form data, such as a filter that sits inside a BbForm: it is not submitted, reset, counted as dirty or validated.

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

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
hasErrors Type: booleanDefaultfalse

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

hasWarnings Type: booleanDefaultfalse

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

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.

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
hideLabel Type: booleanDefaultfalse

Visually hides the label of the input while maintaining accessibility.

hint Type: string

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

id Type: string

The id of the native control; the label's for and the description and message ids derive from it. Generated when omitted.

inputAlign Type: FieldCrossAlign

Vertical position of the control in a side-by-side row; not inputPosition, the horizontal one. Omitted, the control's first line meets the label's.

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.

Raise it to match taller #option markup; a CSS override moves the paint without moving the scroll maths.

See example
itemProps Type: BbSelectItemProps<T>

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.

Only extra fields: description, prepend:icon and append:icon. Text and value come from itemText and itemValue (by default the item's label or text, and its value); no other field is read off the item unless you name it here, and none of it shows in the closed field or the chips. A description makes every row two lines tall.

See example
items requiredType: T[] | ((context: BbSelectItemsContext) => T[] | Promise<T[]>)

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

Pass your domain objects as they are and describe them with item-text and item-value; objects with label and value keys need neither. Pass a function when the list is too large to ship up front; it receives one object, { query, reason, modelValue, signal }, and on the calls the component makes by itself (every reason but 'search') it must also return the selected rows, so their labels resolve.

See example
itemText Type: ItemAccessor<T, string>Defaultown label, else text, else JSON.stringify(item)

Defines a path that returns a property of the object to use as text or a function that returns a string. Unset: own label, else text ({ label, value } needs no accessor).

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

Defines a path that returns a property of the object to use as value or a function that returns any value. Unset: own value, even null.

Prefer a primitive id: stable, unique, cheap to submit. Omit it and the item's own value key becomes the value, or the whole object when it has none; matching still uses its value hash, but the model then carries the full object.

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

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

label requiredType: string

Text content of the label of the element.

labelAlign Type: FieldCrossAlign

Vertical position of the label block in a side-by-side row; not labelPosition, the horizontal one. Omitted, the label's first line meets the control's.

labelMode Type: "outside" | "floating" | "inside"

Label rendering mode.

labelPosition Type: "left" | "center" | "right"

Sets the text alignment of the label.

layout Type: Responsive<FieldLayout>

How label and input sit together: 'vertical', 'horizontal' (50/50), 'auto', 'label-fill', 'input-fill', a ratio like 'xx xxxxx', or a per-breakpoint map ({ default: 'vertical', md: 'xx xxxxx' }).

loading Type: booleanDefaultfalse

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

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

See example
loadingText Type: string

String displayed while items are being loaded.

max Type: number

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
maxSelectedLabels Type: numberDefaultInfinity

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 whole chip row is replaced by a count ("3 items selected", or the text of your selectedLabelsFn), which is not rendered through the chip slot. Unset, the row collapses only when the chips overflow the field.

See example
modelValue Type: any

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

modelValueDebounceTime Type: numberDefault0

Timeout used to debounce response to changes to modelValue.

multiple Type: booleanDefaultfalse

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

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

persistentHint Type: booleanDefaultfalse

Keeps the hint displayed.

placeholder Type: string

Text shown in the field while nothing is picked (an empty list has emptyText).

Shows whenever nothing is selected, in multiple and comma mode too, and names the empty field for assistive tech. It is not a label: keep label set.

See example
prefill Type: boolean | "interaction"Default'interaction'

When the options first load: 'interaction' on first open, true on mount, false only once the user types (on open with disable-writing). An array items or a seeded model always loads at once.

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

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

pt Type: ({ root?: PtValue | undefined; header?: PtValue | undefined; icon?: PtValue | undefined; panel?: PtValue | undefined; "root:open"?: PtValue | undefined; "header:open"?: PtValue | undefined; "icon:open"?: PtValue | undefined; "panel:open"?: PtValue | undefined; spinner?: PtValue | undefined; clear?: PtValue | undefined; "root:loading"?: PtValue | undefined; "icon:loading"?: PtValue | undefined; "spinner:loading"?: PtValue | undefined; "clear:loading"?: PtValue | undefined; "root:disabled"?: PtValue | undefined; label?: PtValue | undefined; "root:errors"?: PtValue | undefined; "root:warnings"?: PtValue | undefined; "root:readonly"?: PtValue | undefined; "icon:disabled"?: PtValue | undefined; "spinner:disabled"?: PtValue | undefined; "root:selected"?: PtValue | undefined; "header:disabled"?: PtValue | undefined; "header:selected"?: PtValue | undefined; description?: PtValue | undefined; message?: PtValue | undefined; hint?: PtValue | undefined; "icon:errors"?: PtValue | undefined; "icon:warnings"?: PtValue | undefined; "icon:readonly"?: PtValue | undefined; "label:disabled"?: PtValue | undefined; "label:errors"?: PtValue | undefined; "label:warnings"?: PtValue | undefined; "label:readonly"?: PtValue | undefined; "description:disabled"?: PtValue | undefined; "description:errors"?: PtValue | undefined; "description:warnings"?: PtValue | undefined; "description:readonly"?: PtValue | undefined; "message:disabled"?: PtValue | undefined; "message:errors"?: PtValue | undefined; "message:warnings"?: PtValue | undefined; "message:readonly"?: PtValue | undefined; "hint:disabled"?: PtValue | undefined; "hint:errors"?: PtValue | undefined; "hint:warnings"?: PtValue | undefined; "hint:readonly"?: PtValue | undefined; input?: PtValue | undefined; list?: PtValue | undefined; "root:hasValue"?: PtValue | undefined; "description:loading"?: PtValue | undefined; "description:hasValue"?: PtValue | undefined; "message:loading"?: PtValue | undefined; "message:hasValue"?: PtValue | undefined; "hint:loading"?: PtValue | undefined; "hint:hasValue"?: PtValue | undefined; "list:loading"?: PtValue | undefined; "list:disabled"?: PtValue | undefined; "list:errors"?: PtValue | undefined; "list:warnings"?: PtValue | undefined; "list:readonly"?: PtValue | undefined; "list:hasValue"?: PtValue | undefined; "icon:hasValue"?: PtValue | undefined; "label:loading"?: PtValue | undefined; "label:hasValue"?: PtValue | undefined; "itemDescription:loading"?: PtValue | undefined; "itemDescription:disabled"?: PtValue | undefined; "itemDescription:errors"?: PtValue | undefined; "itemDescription:warnings"?: PtValue | undefined; "itemDescription:readonly"?: PtValue | undefined; "itemDescription:hasValue"?: PtValue | undefined; box?: PtValue | undefined; prefix?: PtValue | undefined; suffix?: PtValue | undefined; sheet?: PtValue | undefined; "panel:loading"?: PtValue | undefined; "panel:disabled"?: PtValue | undefined; "panel:errors"?: PtValue | undefined; "panel:warnings"?: PtValue | undefined; "panel:readonly"?: PtValue | undefined; "panel:hasValue"?: PtValue | undefined; "spinner:errors"?: PtValue | undefined; "spinner:warnings"?: PtValue | undefined; "spinner:readonly"?: PtValue | undefined; "spinner:hasValue"?: PtValue | undefined; "clear:disabled"?: PtValue | undefined; "clear:errors"?: PtValue | undefined; "clear:warnings"?: PtValue | undefined; "clear:readonly"?: PtValue | undefined; "clear:hasValue"?: PtValue | undefined; "input:loading"?: PtValue | undefined; "input:disabled"?: PtValue | undefined; "input:errors"?: PtValue | undefined; "input:warnings"?: PtValue | undefined; "input:readonly"?: PtValue | undefined; "input:hasValue"?: PtValue | undefined; "box:loading"?: PtValue | undefined; "box:disabled"?: PtValue | undefined; "box:errors"?: PtValue | undefined; "box:warnings"?: PtValue | undefined; "box:readonly"?: PtValue | undefined; "box:hasValue"?: PtValue | undefined; "prefix:loading"?: PtValue | undefined; "prefix:disabled"?: PtValue | undefined; "prefix:errors"?: PtValue | undefined; "prefix:warnings"?: PtValue | undefined; "prefix:readonly"?: PtValue | undefined; "prefix:hasValue"?: PtValue | undefined; "suffix:loading"?: PtValue | undefined; "suffix:disabled"?: PtValue | undefined; "suffix:errors"?: PtValue | undefined; "suffix:warnings"?: PtValue | undefined; "suffix:readonly"?: PtValue | undefined; "suffix:hasValue"?: PtValue | undefined; "sheet:loading"?: PtValue | undefined; "sheet:disabled"?: PtValue | undefined; "sheet:errors"?: PtValue | undefined; "sheet:warnings"?: PtValue | undefined; "sheet:readonly"?: PtValue | undefined; "sheet:hasValue"?: PtValue | undefined; footer?: PtValue | undefined; activator?: PtValue | undefined; "sheet:open"?: PtValue | undefined; "footer:open"?: PtValue | undefined; "panel:selected"?: PtValue | undefined; "sheet:selected"?: PtValue | undefined; "header:loading"?: PtValue | undefined; "header:errors"?: PtValue | undefined; "header:warnings"?: PtValue | undefined; "header:readonly"?: PtValue | undefined; "header:hasValue"?: PtValue | undefined; "icon:selected"?: PtValue | undefined; "spinner:open"?: PtValue | undefined; "spinner:selected"?: PtValue | undefined; "clear:open"?: PtValue | undefined; "clear:selected"?: PtValue | undefined; "label:open"?: PtValue | undefined; "label:selected"?: PtValue | undefined; "description:open"?: PtValue | undefined; "description:selected"?: PtValue | undefined; "message:open"?: PtValue | undefined; "message:selected"?: PtValue | undefined; "hint:open"?: PtValue | undefined; "hint:selected"?: PtValue | undefined; "input:open"?: PtValue | undefined; "input:selected"?: PtValue | undefined; "box:open"?: PtValue | undefined; "box:selected"?: PtValue | undefined; "prefix:open"?: PtValue | undefined; "prefix:selected"?: PtValue | undefined; "suffix:open"?: PtValue | undefined; "suffix:selected"?: PtValue | undefined; "activator:open"?: PtValue | undefined; "activator:selected"?: PtValue | undefined; "activator:loading"?: PtValue | undefined; "activator:disabled"?: PtValue | undefined; "activator:errors"?: PtValue | undefined; "activator:warnings"?: PtValue | undefined; "activator:readonly"?: PtValue | undefined; "activator:hasValue"?: PtValue | undefined; "list:open"?: PtValue | undefined; "list:selected"?: PtValue | undefined; "itemDescription:open"?: PtValue | undefined; "itemDescription:selected"?: PtValue | undefined; "itemText:open"?: PtValue | undefined; "itemText:disabled"?: PtValue | undefined; "itemText:selected"?: PtValue | undefined; "footer:disabled"?: PtValue | undefined; "footer:selected"?: PtValue | undefined; "itemIcon:open"?: PtValue | undefined; "itemIcon:disabled"?: PtValue | undefined; "itemIcon:selected"?: PtValue | undefined; "footer:loading"?: PtValue | undefined; "itemText:loading"?: PtValue | undefined; "itemIcon:loading"?: PtValue | undefined; "itemText:errors"?: PtValue | undefined; "itemText:warnings"?: PtValue | undefined; "itemText:readonly"?: PtValue | undefined; "itemText:hasValue"?: PtValue | undefined; "footer:errors"?: PtValue | undefined; "footer:warnings"?: PtValue | undefined; "footer:readonly"?: PtValue | undefined; "footer:hasValue"?: PtValue | undefined; "itemIcon:errors"?: PtValue | undefined; "itemIcon:warnings"?: PtValue | undefined; "itemIcon:readonly"?: PtValue | undefined; "itemIcon:hasValue"?: PtValue | undefined; "option:open"?: PtValue | undefined; "option:selected"?: PtValue | undefined; "option:loading"?: PtValue | undefined; "option:disabled"?: PtValue | undefined; "option:errors"?: PtValue | undefined; "option:warnings"?: PtValue | undefined; "option:readonly"?: PtValue | undefined; "option:hasValue"?: PtValue | undefined; "badge:open"?: PtValue | undefined; "badge:selected"?: PtValue | undefined; "badge:loading"?: PtValue | undefined; "badge:disabled"?: PtValue | undefined; "badge:errors"?: PtValue | undefined; "badge:warnings"?: PtValue | undefined; "badge:readonly"?: PtValue | undefined; "badge:hasValue"?: PtValue | undefined; "comma:open"?: PtValue | undefined; "comma:selected"?: PtValue | undefined; "comma:loading"?: PtValue | undefined; "comma:disabled"?: PtValue | undefined; "comma:errors"?: PtValue | undefined; "comma:warnings"?: PtValue | undefined; "comma:readonly"?: PtValue | undefined; "comma:hasValue"?: PtValue | undefined; } & { itemDescription?: PtValue | PtItemFn<T, BbSelectPtState, never, BbSelectSlotRow<T, Known>> | undefined; itemText?: PtValue | PtItemFn<T, BbSelectPtState, never, BbSelectSlotRow<T, Known>> | undefined; itemIcon?: PtValue | PtItemFn<T, BbSelectPtState, never, BbSelectSlotRow<T, Known>> | undefined; option?: PtValue | PtItemFn<T, BbSelectPtState, never, BbSelectSlotRow<T, Known>> | undefined; badge?: PtValue | PtItemFn<T, BbSelectPtState, never, BbSelectSlotRow<T, Known>> | undefined; comma?: PtValue | PtItemFn<T, BbSelectPtState, never, BbSelectSlotRow<T, Known>> | undefined; } & { "root:outline"?: PtValue | undefined; "icon:outline"?: PtValue | undefined; "root:secondary"?: PtValue | undefined; "icon:secondary"?: PtValue | undefined; "spinner:outline"?: PtValue | undefined; "spinner:secondary"?: PtValue | undefined; "clear:outline"?: PtValue | undefined; "clear:secondary"?: PtValue | undefined; "root:ghost"?: PtValue | undefined; "icon:ghost"?: PtValue | undefined; "spinner:ghost"?: PtValue | undefined; "clear:ghost"?: PtValue | undefined; "label:outline"?: PtValue | undefined; "label:secondary"?: PtValue | undefined; "label:ghost"?: PtValue | undefined; "description:outline"?: PtValue | undefined; "description:secondary"?: PtValue | undefined; "description:ghost"?: PtValue | undefined; "message:outline"?: PtValue | undefined; "message:secondary"?: PtValue | undefined; "message:ghost"?: PtValue | undefined; "hint:outline"?: PtValue | undefined; "hint:secondary"?: PtValue | undefined; "hint:ghost"?: PtValue | undefined; "box:outline"?: PtValue | undefined; "box:secondary"?: PtValue | undefined; "box:ghost"?: PtValue | undefined; "prefix:outline"?: PtValue | undefined; "prefix:secondary"?: PtValue | undefined; "prefix:ghost"?: PtValue | undefined; "suffix:outline"?: PtValue | undefined; "suffix:secondary"?: PtValue | undefined; "suffix:ghost"?: PtValue | undefined; "activator:outline"?: PtValue | undefined; "activator:secondary"?: PtValue | undefined; "activator:ghost"?: PtValue | undefined; })

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

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

Passthrough, attribute form: one attribute per part, pt:<part>, and per part and state, pt:<part>:<state>. Parts: activator, badge, box, clear, comma, description, footer, header, hint, icon, input, itemDescription, itemIcon, itemText, label, list, message, option, panel, prefix, root, sheet, spinner, suffix. States: disabled, errors, ghost, hasValue, loading, open, outline, readonly, secondary, selected, warnings.

See example
queryDebounceTime Type: numberDefault500

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

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

See example
readonly Type: booleanDefaultfalse

Sets the input in a readonly state.

required Type: booleanDefaultfalse

Sets the input as required.

Feeds the asterisk (unless the plugin's requiredAsterisk option is off), and blocks nothing on its own: the hidden inputs carrying the value are barred from native constraint validation by the HTML spec. Inside a BbForm, pair it with a required rule; outside one, the message comes from errors.

See example
reverse Type: booleanDefaultfalse

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

rules Type: RuleExpression<any>

vee-validate rules for this field. Only inside a BbForm.

Checked when focus leaves both the field and its panel, then on every change while an error shows (the default balanced mode). A function rule is opaque to the asterisk, so set required on the field as well.

See example
selectable Type: boolean | ((item: T) => 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.

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 Type: ((count: number) => string)

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

stash Type: booleanDefaultfalse

Keeps picked options named across searches: a pick stays resolvable (its chip keeps its text) 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 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
transitionDuration Type: numberDefault250

How long the transition has to last in milliseconds.

validationLabel Type: string

The field's name in validation messages ({field}): "Email is not valid". Defaults to the label (or legend), then "This field"; never the name. Set it when the label is hidden or reads badly in a sentence (icon-only, a question). BbForm only.

validationMode Type: ValidationMode

When the field validates: aggressive (each change), balanced (on leave, then live while in error), lazy (on leave), submit-only, submit-live. Unset: the BbForm's validation-mode, then formControls.validationMode ('balanced'). BbForm only.

variant Type: InputVariantTypeDefault'outline'

The field box's look, with BbButton's names and tokens. Colours only, so a form never reflows; 'ghost' has no border in any state, 'none' no look at all (you own hover, focus and the states). Register more with the plugin's inputVariants.

warnings Type: string | string[]

Warning messages under the field, a string or array: amber, in their own aria-live region and in the control's aria-describedby after the errors. Non-empty turns on the warning state; errors win when both are set.

v-model

Values kept in sync through v-model.

NameDescription
modelValue Type: any

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

NameDescription
active Type: (): void

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

blur Type: (event: FocusEvent): void

Emitted when the activator loses focus. Forwards the original DOM FocusEvent.

change Type: (event: Event): void

Emitted on the native change event. Forwards the original DOM Event.

click Type: (event: MouseEvent): void

Emitted when the activator is clicked. Forwards the original DOM MouseEvent.

focus Type: (event: FocusEvent): void

Emitted when the activator receives focus. Forwards the original DOM FocusEvent.

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 whole select after it was active.

Outside a BbForm, compute your own errors on this, not on blur (inside one, the form already does). 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 Type: (event: Event): void

Emitted on native input events from the search field. Forwards the DOM Event.

option:add Type: (text: string): void

Emitted with the typed text when the user confirms a value that is not in the list (Enter in the search field). Binding it keeps the search field writable, which makes the select creatable.

Fires with the trimmed query when Enter is pressed and no option is highlighted. Create the record on your side, then push it into items so the selection resolves. It can fire with an empty string. Listening to it keeps the search field on short lists; disableWriting removes both.

See example
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 new value whenever v-model changes.

Slots

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

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

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

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

Content appended outside the input field wrapper.

chip Type: BbSelectChipSlotProps<T, Known>

Replaces the default badge rendered for each selection in multiple mode. Not called in comma mode, nor for the collapsed "+N" summary. With known-items bound, item is the known { value, label } row (known: true) until the list brings the real one.

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

See example
description Type: BbSelectDescriptionSlotProps

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

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: BbSelectGroupSlotProps<T>

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.

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

See example
header Type: BbSelectPanelSlotProps

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
label Type: BbSelectLabelSlotProps

Replaces the default label text rendered above the input.

loading Type: { query: string; }

Content shown inside the options dropdown while items are loading.

option Type: BbSelectOptionSlotProps<T, 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.

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
prefix Type: object

Content rendered as an inline prefix inside the input field.

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

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

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

Content prepended outside the input field wrapper.

suffix Type: object

Content rendered as an inline suffix inside the input field.

Renamed and removed

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

  • allowWritingdisableWritingfails silently
  • slotappend-outerappend:outerfails silently
  • directionlayoutfails silently
  • hasWarninghasWarningsfails silently
  • slotno-dataemptyfails silently
  • noDataTextemptyTextfails silently
  • slotprepend-outerprepend:outerfails silently
  • showChevronremovedfails silently
  • updateOnAnimationFrameremovedfails silently
  • validateOnvalidationModefails silently

    An array of events became one mode (aggressive | balanced | lazy | submit-only | submit-live). The old defaults (['blur'], ['inactive'], a toggle's ['update:modelValue', 'change', 'blur']) are balanced, the new default: delete the prop. A text field on ['update:modelValue'] is validation-mode="aggressive". Set it once on <BbForm validation-mode> instead of per field.

  • vidremovedfails silently

    A display name (v2: vid="Email address") is now validation-label; a field path (3.0 betas: vid="address.city") is now name (brackets for a native post: address[city]). check --fix picks by the value.