Skip to content

Combobox ​

Lets people type to find one option in a long list, then choose it.

vue
<script setup lang="ts">
import { FsCombobox, FsField } from '@fivespark/ui'
import { ref } from 'vue'

const landen = [
  { value: 'BE', label: 'België' },
  { value: 'DE', label: 'Duitsland' },
  { value: 'FR', label: 'Frankrijk' },
  { value: 'LU', label: 'Luxemburg' },
  { value: 'NL', label: 'Nederland' },
]
const land = ref<string | null>(null)
</script>

<template>
  <div class="min-h-72 w-full max-w-xs">
    <FsField label="Land" description="Typ een deel van de naam">
      <FsCombobox v-model="land" :options="landen" name="land" />
    </FsField>
  </div>
</template>

Usage ​

vue
<script setup lang="ts">
import { FsCombobox, FsField } from '@fivespark/ui'
import { ref } from 'vue'

const land = ref<string | null>(null)
</script>

<template>
  <FsField label="Land">
    <FsCombobox v-model="land" name="land" :options="[{ value: 'NL', label: 'Nederland' }]" />
  </FsField>
</template>

v-model is the chosen option's value, or null. A combobox only takes an option from the list, never free text. With a short list, a Select is quicker; to choose many, there is no combobox yet.

Examples ​

Groups ​

Give groups of options, each with a label, when every option fits exactly one group.

vue
<script setup lang="ts">
import { type ComboboxGroup, FsCombobox, FsField } from '@fivespark/ui'
import { ref } from 'vue'

const landen: ComboboxGroup[] = [
  { label: 'Benelux', options: [
    { value: 'BE', label: 'België' },
    { value: 'NL', label: 'Nederland' },
    { value: 'LU', label: 'Luxemburg' },
  ] },
  { label: 'Overig Europa', options: [
    { value: 'DE', label: 'Duitsland' },
    { value: 'FR', label: 'Frankrijk' },
  ] },
]
const land = ref<string | null>('NL')
</script>

<template>
  <div class="min-h-72 w-full max-w-xs">
    <FsField label="Land">
      <FsCombobox v-model="land" :options="landen" name="land-groepen" />
    </FsField>
  </div>
</template>

Options from a server ​

Set filter to false, pass only the matches and read what was typed from v-model:query. Fetch with a debounce, set loading while the request runs, and keep the chosen option in options so its label can still be shown.

vue
<script setup lang="ts">
import { FsCombobox, FsField } from '@fivespark/ui'
import { computed, ref } from 'vue'

const klanten = [
  { value: '1', label: 'Bakker BV' },
  { value: '2', label: 'De Vries Installatietechniek' },
  { value: '3', label: 'Jansen Bouw B.V.' },
  { value: '4', label: 'Appel & Co' },
]
const klant = ref<string | null>(null)
const zoek = ref('')
const gevonden = computed(() => klanten.filter(k => k.value === klant.value || k.label.toLowerCase().includes(zoek.value.toLowerCase())))
</script>

<template>
  <div class="min-h-72 w-full max-w-xs">
    <FsField label="Klant">
      <FsCombobox v-model="klant" v-model:query="zoek" :options="gevonden" :filter="false" name="klant" />
    </FsField>
  </div>
</template>

Loading ​

loading shows a loading message in place of "no results" while the options are on their way.

vue
<script setup lang="ts">
import { FsCombobox, FsField } from '@fivespark/ui'
import { ref } from 'vue'

const klant = ref<string | null>(null)
const zoek = ref('')
</script>

<template>
  <div class="min-h-72 w-full max-w-xs">
    <FsField label="Klant">
      <FsCombobox v-model="klant" v-model:query="zoek" :options="[]" :filter="false" loading name="klant-laden" />
    </FsField>
  </div>
</template>

Invalid ​

vue
<script setup lang="ts">
import { FsCombobox, FsField } from '@fivespark/ui'
import { ref } from 'vue'

const plaatsen = [
  { value: 'utrecht', label: 'Utrecht' },
  { value: 'rotterdam', label: 'Rotterdam' },
  { value: 'breda', label: 'Breda' },
]
const plaats = ref<string | null>(null)
</script>

<template>
  <div class="min-h-72 w-full max-w-xs">
    <FsField label="Plaats" required error="Kies een plaats uit de lijst.">
      <FsCombobox v-model="plaats" :options="plaatsen" name="plaats" />
    </FsField>
  </div>
</template>

Disabled ​

vue
<script setup lang="ts">
import { FsCombobox, FsField } from '@fivespark/ui'
import { ref } from 'vue'

const plaatsen = [
  { value: 'utrecht', label: 'Utrecht' },
  { value: 'rotterdam', label: 'Rotterdam' },
]
const plaats = ref<string | null>('utrecht')
</script>

<template>
  <FsField label="Plaats" disabled class="w-full max-w-xs">
    <FsCombobox v-model="plaats" :options="plaatsen" name="plaats-uit" />
  </FsField>
</template>

Read-only ​

readonly on the field shows the choice without letting it change.

vue
<script setup lang="ts">
import { FsCombobox, FsField } from '@fivespark/ui'
import { ref } from 'vue'

const plaatsen = [
  { value: 'utrecht', label: 'Utrecht' },
  { value: 'rotterdam', label: 'Rotterdam' },
]
const plaats = ref<string | null>('utrecht')
</script>

<template>
  <FsField label="Plaats" readonly class="w-full max-w-xs">
    <FsCombobox v-model="plaats" :options="plaatsen" name="plaats-vast" />
  </FsField>
</template>

Keyboard ​

KeyDoes
TypingOpens the list, filtered by the text
↓, ↑Opens the list, or moves to the next or previous option; wraps around
EnterChooses the highlighted option and closes the list; with the list closed it submits the form
EscCloses the list and restores the chosen option's label
TabChooses the option the arrow keys reached, if any, and moves on
Alt + ↓, Alt + ↑Opens the list without highlighting an option; closes it and keeps the text

API Reference ​

FsCombobox ​

PropTypeDefaultDescription
options(ComboboxOption | ComboboxGroup)[]requiredThe options, and groups of them, in the order shown.
filterbooleantrueFilters `options` by the text typed, ignoring case and accents. `false` when the app filters, from `v-model:query`, and passes only the matches.
loadingbooleanfalseThe app is fetching options: a spinner in the field, and "Laden…" in place of an empty list.
size"default" | "dense"`default` is 36px high; `dense` is 32px, for tables and toolbars. In a field, the field's.
idstringOnly outside a field; in one, it takes the field's.
invalidbooleanMarks the value as wrong; the field says why. In a field, the field's.
disabledbooleanUnavailable until something else is done: not focusable, not submitted. A value to see but not change is `readonly`. In a field, the field's.
readonlybooleanA value to see and copy, not change: the chosen option's label, with no list. In a field, the field's.
requiredbooleanIn a field, the field's, which also shows the asterisk.
modelValuestring | nullnullThe chosen option's `value`, or `null` when none. Without `v-model` it keeps its own.
querystring""The text typed: `""` until the user types, and again once an option is chosen or the field is left. Read it to fetch options.
EventPayloadDescription
update:modelValue[value: string | null]Sent with the new `modelValue`, for `v-model`.
update:query[value: string]Sent with the new `query`, for `v-model:query`.