Component
Combobox
<dom-combobox>A styled select-like control: show the option label, store the option value, and keep keyboard navigation.
Playground
Custom item playground
People combobox playground
Edit people rows in the inspector and the custom item slot keeps rendering the richer markup.
Ada LovelaceMathematician
Grace HopperComputer scientist
Katherine JohnsonNASA mathematician
Selected value: ada
<script setup>
import { reactive } from 'vue';
import { DomCombobox } from '@getdom/studio/vue';
const data = reactive({
modelValue: 'ada',
options: [
{ value: 'ada', label: 'Ada Lovelace', role: 'Mathematician', avatar: 'https://unavatar.io/github/adafruit' },
{ value: 'grace', label: 'Grace Hopper', role: 'Computer scientist', avatar: 'https://unavatar.io/github/ghopper' },
{ value: 'katherine', label: 'Katherine Johnson', role: 'NASA mathematician', avatar: 'https://unavatar.io/github/nasa' },
],
placeholder: 'Assign a person',
placement: 'bottom',
floatingMode: 'viewport',
clearable: true,
loading: false,
});
defineExpose({ data });
</script>
<template>
<div class="flex w-full max-w-md flex-col gap-2">
<DomCombobox
v-model="data.modelValue"
:options="data.options"
:placeholder="data.placeholder"
:placement="data.placement"
:floating-mode="data.floatingMode"
:clearable="data.clearable"
:loading="data.loading"
class="w-full"
>
<template #item="{ item }">
<div class="flex min-w-0 items-center gap-3">
<img :src="item.avatar" alt="" class="size-8 rounded-full bg-secondary object-cover" />
<span class="min-w-0">
<span class="block truncate font-medium">{{ item.label }}</span>
<span class="block truncate text-xs text-muted-fg">{{ item.role }}</span>
</span>
</div>
</template>
</DomCombobox>
<p class="text-xs text-muted-fg">Selected value: <code class="text-fg">{{ data.modelValue || '—' }}</code></p>
</div>
</template>
Demo
Vue
The input displays the label, while v-model receives the option value.
- Apple
- Banana
- Cherry
Selected value: —
<script setup>
import { DomCombobox } from '@getdom/studio/vue';
import { ref } from 'vue';
const value = ref('');
const options = [
{ value: '1', label: 'Apple' },
{ value: '2', label: 'Banana' },
{ value: '3', label: 'Cherry' },
];
</script>
<template>
<div class="flex w-full max-w-sm flex-col gap-2">
<DomCombobox v-model="value" :options="options" placeholder="Pick a fruit" class="w-full" />
<p class="text-xs text-muted-fg">Selected value: <code class="text-fg">{{ value || '—' }}</code></p>
</div>
</template>
Demo
Custom item markup
Use the item slot for richer option rendering. The input still displays the label and v-model still receives the value.
Ada LovelaceMathematician
Grace HopperComputer scientist
Katherine JohnsonNASA mathematician
Selected value: —
<script setup>
import { DomCombobox } from '@getdom/studio/vue';
import { ref } from 'vue';
const value = ref('');
const people = [
{ value: 'ada', label: 'Ada Lovelace', role: 'Mathematician', avatar: 'https://unavatar.io/github/adafruit' },
{ value: 'grace', label: 'Grace Hopper', role: 'Computer scientist', avatar: 'https://unavatar.io/github/ghopper' },
{ value: 'katherine', label: 'Katherine Johnson', role: 'NASA mathematician', avatar: 'https://unavatar.io/github/nasa' },
];
</script>
<template>
<div class="flex w-full max-w-md flex-col gap-2">
<DomCombobox v-model="value" :options="people" placeholder="Assign a person" class="w-full">
<template #item="{ item }">
<div class="flex items-center gap-3">
<img :src="item.avatar" alt="" class="size-8 rounded-full bg-secondary object-cover" />
<span class="min-w-0">
<span class="block truncate font-medium">{{ item.label }}</span>
<span class="block truncate text-xs text-muted-fg">{{ item.role }}</span>
</span>
</div>
</template>
</DomCombobox>
<p class="text-xs text-muted-fg">Selected value: <code class="text-fg">{{ value || '—' }}</code></p>
</div>
</template>
Demo
Server-loaded options
@query lets the parent fetch options and pass the current server results back through options.
Options are fetched by the parent in response to @query.
Selected value: None
<script setup>
import { ref } from 'vue';
import { DomCombobox } from '@getdom/studio/vue';
import { searchPeople } from '../../_shared/serverLookup.js';
const value = ref('');
const options = ref([]);
const loading = ref(false);
let requestId = 0;
async function queryPeople(query) {
const currentRequest = ++requestId;
if (!query.trim()) {
options.value = [];
loading.value = false;
return;
}
loading.value = true;
const results = await searchPeople(query);
if (currentRequest !== requestId) return;
options.value = results;
loading.value = false;
}
</script>
<template>
<div class="grid w-full max-w-md gap-3">
<DomCombobox
v-model="value"
:options="options"
:loading="loading"
label="Assign owner"
description="Options are fetched by the parent in response to @query."
placeholder="Type a name"
class="w-full"
@query="queryPeople"
>
<template #item="{ item }">
<span class="block min-w-0">
<span class="block truncate font-medium">{{ item.label }}</span>
<span class="block truncate text-xs text-muted-fg">{{ item.email }} - {{ item.role }}</span>
</span>
</template>
</DomCombobox>
<p class="rounded-2xl border border-border bg-secondary/40 p-3 text-xs text-muted-fg">
Selected value: <code class="font-mono text-fg">{{ value || 'None' }}</code>
</p>
</div>
</template>
Usage
Plain HTML
Use the headless custom element when you want the same combobox behaviour in plain HTML or another framework. The headless page includes copyable DOM-defined list examples.
View headless comboboxProps
Props
| Name | Type | Default | Description |
|---|---|---|---|
| v-model | string | — | Selected option value. |
| options | Array<{ value, label }> | string[] | — | Available options. |
| placeholder | string | 'Search…' | Input placeholder. |
| floatingMode | 'viewport' | 'anchor' | 'viewport' | Choose whether the list stays in the browser or follows the input while scrolling. |
| clearable | boolean | true | Show an inline clear button when an option is selected. |
| loading | boolean | false | Show an inline spinner while async options are being fetched. |
Keyboard
- ↑ / ↓Move active option.
- EnterCommit active option.
- EscClose the list.
- TypeFilter the list live.