Skip to content

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


Quick Start

Import

ts
import { Combobox } from '@pharma4u/patternlab/vue'

Basic usage

vue
<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
vue
<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
vue
<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
vue
<template>
  <Combobox
    v-model="value"
    label="Pflichtfeld"
    :options="items"
    required
    error="Bitte wählen Sie einen Eintrag aus." />
</template>

Props

PropTypeDefaultDescription
modelValuestring | number | nullnullSelected value (v-model).
optionsSelectItem[] | Record<string, string>requiredFlat options ({label, value}[]), grouped options ({label, options}[]), or a plain value→label map.
selectedOptionsSelectItem[] | 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).
labelstringrequiredField label (from BaseInputFieldWithIconsProps).
placeholderstring'Bitte wählen ...'Text shown in the trigger when nothing is selected.
customOptionsbooleanfalseWhen true, typing a search value with no match offers it as a selectable custom option.
customOptionNamestring | nullnullLabel prefix shown for the generated custom option (also enables customOptions behavior on its own).
loadingbooleanfalseShows a spinner in the search field and suppresses the custom-option / empty-state logic while true.
noResultsTextstring'Keine Übereinstimmungen gefunden.'Text shown when options is empty.
idstringauto-generatedCustom ID for the trigger element.
hintstringundefinedHint text rendered below the field.
errorstringundefinedError message. When set, also forces the error validation state.
validationState'error' | 'warning' | 'success'undefinedExplicit validation state when no error message is set.
disabledbooleanfalseDisables the trigger.
requiredbooleanfalseShows the required marker (*) next to the label.
lightbooleanfalseLight visual variant of the field shell.
hiddenLabelbooleanfalseVisually hides the label (still available to assistive tech).
floatContainerbooleantrueWraps the field in the c-float-container layout.
containerClassstring | string[] | Record<string, boolean>''Extra class(es) for the field container.
iconLeft / iconRightstring''CSS class(es) for a left/right icon (ignored if the matching slot is used).
hasSmallIconbooleanfalseRenders the left icon in a smaller size.
leftIconInteractive / rightIconInteractivebooleanfalseRenders 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

EventPayloadDescription
update:modelValuestring | number | nullEmitted on selection. Used by v-model.
select-itemSelectOption | nullEmitted alongside update:modelValue with the full selected option (label + value).
searchstringEmitted on every keystroke in the search field.
openEmitted when the dropdown opens.
closeEmitted when the dropdown closes.

Slots

SlotPropsDescription
leftIcon / rightIconReplace 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" with aria-expanded, aria-controls, aria-invalid and aria-describedby.
  • The dropdown list renders role="listbox" / role="option" and keeps aria-selected in sync with the highlighted/selected option.
  • Keyboard support: ArrowDown / ArrowUp move the highlight, Enter selects, Escape closes.
  • 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 mousedown listener 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.
  • Combobox is composed from smaller, individually exported pieces: BaseInputField (generic field shell), ComboboxPanel, ComboboxOptions, ComboboxSearch, and the useComboboxOptions composable — all available from @pharma4u/patternlab/vue if you need to build a custom variant.