Appearance
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
| Key | Does |
|---|---|
| Typing | Opens the list, filtered by the text |
| ↓, ↑ | Opens the list, or moves to the next or previous option; wraps around |
| Enter | Chooses the highlighted option and closes the list; with the list closed it submits the form |
| Esc | Closes the list and restores the chosen option's label |
| Tab | Chooses 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
| Prop | Type | Default | Description |
|---|---|---|---|
options | (ComboboxOption | ComboboxGroup)[] | required | The options, and groups of them, in the order shown. |
filter | boolean | true | Filters `options` by the text typed, ignoring case and accents. `false` when the app filters, from `v-model:query`, and passes only the matches. |
loading | boolean | false | The 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. | |
id | string | Only outside a field; in one, it takes the field's. | |
invalid | boolean | Marks the value as wrong; the field says why. In a field, the field's. | |
disabled | boolean | Unavailable until something else is done: not focusable, not submitted. A value to see but not change is `readonly`. In a field, the field's. | |
readonly | boolean | A value to see and copy, not change: the chosen option's label, with no list. In a field, the field's. | |
required | boolean | In a field, the field's, which also shows the asterisk. | |
modelValue | string | null | null | The chosen option's `value`, or `null` when none. Without `v-model` it keeps its own. |
query | string | "" | The text typed: `""` until the user types, and again once an option is chosen or the field is left. Read it to fetch options. |
| Event | Payload | Description |
|---|---|---|
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`. |