Combobox (Vue Component)
Combobox is a searchable, accessible select field implemented as a Vue component. It renders a trigger button styled exactly like the .c-select fields above, and a teleported dropdown panel with a search input and a keyboard-navigable option list — all interaction (open/close, search, keyboard navigation, positioning) is implemented in Vue, with no jQuery/select2.js dependency.
When to use
Use Combobox instead of .js-select2 whenever you're building a Vue app and need a searchable dropdown driven by reactive data — e.g. filtering options as the user types by reacting to the search event (the component itself doesn't filter, it just emits the raw search value so you can update :options yourself, which also covers server-side/AJAX search), or letting the user type a value that isn't in the list (customOptions). For static, non-Vue pages, keep using .js-select2 (see Select2).
- Styling based on: Select2
- Design: Figma Component: Select
Quick Start
Import
import { Combobox } from '@pharma4u/patternlab/vue'Basic usage
<Combobox v-model="selectedItem" label="Item" :options="items" />Positioning
Combobox positions its dropdown with @floating-ui/vue (^1.1.11), which is a regular dependency of patternlab — it's installed transitively, you don't need to add it to your own project.
Live Examples
Search Filtering (client-side)
Combobox does not filter its own option list — it only emits search with the typed text. Here we filter allItems ourselves in a computed and feed the result back via :options. The same @search handler could instead call an API and pass back a loading state, which is how it's used for server-side/AJAX search in production (labxpert-abfuellung's useRemoteComboboxSearch).
Show Code
<script setup>
import { computed, ref } from 'vue'
import { Combobox } from '@pharma4u/patternlab/vue'
const value = ref(null)
const allItems = [
{ label: 'First item', value: '1' },
{ label: 'Second item', value: '2' },
{ label: 'Third item', value: '3' },
{ label: 'Fourth item', value: '4' },
{ label: 'Fifth item', value: '5' },
{ label: 'Sixth item', value: '6' },
{ label: 'Seventh item', value: '7' },
{ label: 'Eighth item', value: '8' },
{ label: 'Just an item', value: '9' },
{ label: 'Last item', value: '10' },
]
const filterTerm = ref('')
const filteredItems = computed(() => {
const term = filterTerm.value.trim().toLowerCase()
if (!term) return allItems
return allItems.filter((option) => option.label.toLowerCase().includes(term))
})
</script>
<template>
<Combobox
v-model="value"
label="Item (mit Filterung)"
:options="filteredItems"
no-results-text="Keine Einträge gefunden."
@search="filterTerm = $event" />
</template>Custom Options (free text)
With custom-options, typing a value that does not match any existing option adds it as a selectable "create new" entry.
Show Code
<template>
<Combobox
v-model="selectedCustomItem"
label="Item"
:options="customItems.map(i => ({ label: i, value: i }))"
custom-options
custom-option-name="Neues Item" />
</template>Error State
Show Code
<template>
<Combobox
v-model="value"
label="Pflichtfeld"
:options="items"
required
error="Bitte wählen Sie einen Eintrag aus." />
</template>Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | string | number | null | null | Selected value (v-model). |
options | SelectItem[] | Record<string, string> | — required | Flat options ({label, value}[]), grouped options ({label, options}[]), or a plain value→label map. |
selectedOptions | SelectItem[] | Record<string, string> | [] | Extra options used to resolve the display label for modelValue when it isn't present in options (e.g. lazily-loaded lists). |
label | string | — required | Field label (from BaseInputFieldWithIconsProps). |
placeholder | string | 'Bitte wählen ...' | Text shown in the trigger when nothing is selected. |
customOptions | boolean | false | When true, typing a search value with no match offers it as a selectable custom option. |
customOptionName | string | null | null | Label prefix shown for the generated custom option (also enables customOptions behavior on its own). |
loading | boolean | false | Shows a spinner in the search field and suppresses the custom-option / empty-state logic while true. |
noResultsText | string | 'Keine Übereinstimmungen gefunden.' | Text shown when options is empty. |
id | string | auto-generated | Custom ID for the trigger element. |
hint | string | undefined | Hint text rendered below the field. |
error | string | undefined | Error message. When set, also forces the error validation state. |
validationState | 'error' | 'warning' | 'success' | undefined | Explicit validation state when no error message is set. |
disabled | boolean | false | Disables the trigger. |
required | boolean | false | Shows the required marker (*) next to the label. |
light | boolean | false | Light visual variant of the field shell. |
hiddenLabel | boolean | false | Visually hides the label (still available to assistive tech). |
floatContainer | boolean | true | Wraps the field in the c-float-container layout. |
containerClass | string | string[] | Record<string, boolean> | '' | Extra class(es) for the field container. |
iconLeft / iconRight | string | '' | CSS class(es) for a left/right icon (ignored if the matching slot is used). |
hasSmallIcon | boolean | false | Renders the left icon in a smaller size. |
leftIconInteractive / rightIconInteractive | boolean | false | Renders the icon outside the pointer-events-disabled wrapper so it can have its own click handler. |
No built-in validation framework
Combobox does not integrate with any specific form/validation library. Drive error / validationState from whatever validation approach your app already uses.
Events
| Event | Payload | Description |
|---|---|---|
update:modelValue | string | number | null | Emitted on selection. Used by v-model. |
select-item | SelectOption | null | Emitted alongside update:modelValue with the full selected option (label + value). |
search | string | Emitted on every keystroke in the search field. |
open | — | Emitted when the dropdown opens. |
close | — | Emitted when the dropdown closes. |
Slots
| Slot | Props | Description |
|---|---|---|
leftIcon / rightIcon | — | Replace the icon markup generated from iconLeft / iconRight. |
empty | { noResultsText } | Replace the "no results" list item. |
option | { option, index, selected, highlighted, customLabel } | Replace the rendering of a single option row. |
Accessibility
- The trigger renders
role="combobox"witharia-expanded,aria-controls,aria-invalidandaria-describedby. - The dropdown list renders
role="listbox"/role="option"and keepsaria-selectedin sync with the highlighted/selected option. - Keyboard support:
ArrowDown/ArrowUpmove the highlight,Enterselects,Escapecloses. - The search input is auto-focused once the dropdown has finished positioning.
- Clicking outside the trigger and panel closes the dropdown (via a document-level
mousedownlistener that is added/removed with the open state).
Notes
- The dropdown panel is rendered via
<Teleport to="body">and positioned with@floating-ui/vue(flip+shift+autoUpdate), matching the trigger's width and flipping above it when there isn't enough room below. Comboboxis composed from smaller, individually exported pieces:BaseInputField(generic field shell),ComboboxPanel,ComboboxOptions,ComboboxSearch, and theuseComboboxOptionscomposable — all available from@pharma4u/patternlab/vueif you need to build a custom variant.