<script setup>
import { ref } from "vue";
import { Combobox, createComboboxCollection } from "@dicehub/phi/components/combobox";
const value = ref(["apple"]);
const fruits = [
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
{ label: "Cherry", value: "cherry" },
];
const collection = createComboboxCollection({
items: fruits,
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
});
</script>
<template>
<Combobox v-model="value" :collection="collection">
<Combobox.TriggerInput placeholder="Please select" />
<Combobox.Content>
<Combobox.Empty />
<Combobox.List>
<template #default="{ item }">
<Combobox.Item :item="item">{{ item.label }}</Combobox.Item>
</template>
</Combobox.List>
</Combobox.Content>
</Combobox>
</template>Installation
Barrel
import { Combobox } from "@dicehub/phi";Granular
import { Combobox } from "@dicehub/phi/components/combobox";Usage
Standard Combobox.TriggerInput, Combobox.TriggerValue, andCombobox.TriggerMultipleWithInput triggers show the same up/down double chevron as Select. Replace it with your own control through as-child, or through thetrigger slot on Combobox.TriggerInput, when you need a custom indicator.
<script setup>
import { ref } from "vue";
import { Combobox, createComboboxCollection } from "@dicehub/phi/components/combobox";
const value = ref(["banana"]);
const fruits = [
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
{ label: "Cherry", value: "cherry" },
];
const collection = createComboboxCollection({
items: fruits,
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
});
</script>
<template>
<Combobox v-model="value" :collection="collection">
<Combobox.TriggerInput placeholder="Select a fruit" />
<Combobox.Content>
<Combobox.Empty />
<Combobox.List>
<template #default="{ item }">
<Combobox.Item :item="item">{{ item.label }}</Combobox.Item>
</template>
</Combobox.List>
</Combobox.Content>
</Combobox>
</template>Examples
Sizes
Trigger Input
<script setup>
import { ref } from "vue";
import { Combobox, createComboboxCollection } from "@dicehub/phi/components/combobox";
const value = ref(["apple"]);
const collection = createComboboxCollection({
items: [{ label: "Apple", value: "apple" }, { label: "Banana", value: "banana" }],
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
});
</script>
<template>
<div class="grid gap-3">
<Combobox v-model="value" :collection="collection" size="sm">
<Combobox.TriggerInput placeholder="Small" />
<Combobox.Content>
<Combobox.Empty />
<Combobox.List>
<template #default="{ item }">
<Combobox.Item :item="item">{{ item.label }}</Combobox.Item>
</template>
</Combobox.List>
</Combobox.Content>
</Combobox>
<Combobox v-model="value" :collection="collection">
<Combobox.TriggerInput placeholder="Base" />
<Combobox.Content>
<Combobox.Empty />
<Combobox.List>
<template #default="{ item }">
<Combobox.Item :item="item">{{ item.label }}</Combobox.Item>
</template>
</Combobox.List>
</Combobox.Content>
</Combobox>
</div>
</template>Trigger Value
<script setup>
import { ref } from "vue";
import { Combobox, createComboboxCollection } from "@dicehub/phi/components/combobox";
const value = ref(["en"]);
const collection = createComboboxCollection({
items: [
{ emoji: "🇬🇧", label: "English", value: "en" },
{ emoji: "🇫🇷", label: "French", value: "fr" },
{ emoji: "🇩🇪", label: "German", value: "de" },
],
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
});
</script>
<template>
<div class="grid gap-3">
<Combobox v-model="value" :collection="collection" size="sm">
<Combobox.TriggerValue placeholder="Small" />
<Combobox.Content>
<Combobox.Input placeholder="Search" />
<Combobox.Empty />
<Combobox.List>
<template #default="{ item }">
<Combobox.Item :item="item">{{ item.emoji }} {{ item.label }}</Combobox.Item>
</template>
</Combobox.List>
</Combobox.Content>
</Combobox>
<Combobox v-model="value" :collection="collection">
<Combobox.TriggerValue placeholder="Base" />
<Combobox.Content>
<Combobox.Input placeholder="Search" />
<Combobox.Empty />
<Combobox.List>
<template #default="{ item }">
<Combobox.Item :item="item">{{ item.emoji }} {{ item.label }}</Combobox.Item>
</template>
</Combobox.List>
</Combobox.Content>
</Combobox>
</div>
</template>Searchable Item Inside
<script setup>
import { ref } from "vue";
import { Combobox, createComboboxCollection } from "@dicehub/phi/components/combobox";
const value = ref(["en"]);
const languages = [
{ emoji: "🇬🇧", label: "English", value: "en" },
{ emoji: "🇫🇷", label: "French", value: "fr" },
{ emoji: "🇩🇪", label: "German", value: "de" },
];
const collection = createComboboxCollection({
items: languages,
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
});
</script>
<template>
<Combobox v-model="value" :collection="collection">
<Combobox.TriggerValue placeholder="Select a language" />
<Combobox.Content>
<Combobox.Input placeholder="Search languages" />
<Combobox.Empty />
<Combobox.List>
<template #default="{ item }">
<Combobox.Item :item="item">{{ item.emoji }} {{ item.label }}</Combobox.Item>
</template>
</Combobox.List>
</Combobox.Content>
</Combobox>
</template>Searchable Select with Placeholder
<script setup>
import { ref } from "vue";
import { Combobox, createComboboxCollection } from "@dicehub/phi/components/combobox";
const value = ref([]);
const languages = [
{ emoji: "🇬🇧", label: "English", value: "en" },
{ emoji: "🇫🇷", label: "French", value: "fr" },
{ emoji: "🇩🇪", label: "German", value: "de" },
];
const collection = createComboboxCollection({
items: languages,
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
});
</script>
<template>
<Combobox v-model="value" :collection="collection">
<Combobox.TriggerValue placeholder="Select a language" />
<Combobox.Content>
<Combobox.Input placeholder="Search languages" />
<Combobox.Empty />
<Combobox.List>
<template #default="{ item }">
<Combobox.Item :item="item">{{ item.emoji }} {{ item.label }}</Combobox.Item>
</template>
</Combobox.List>
</Combobox.Content>
</Combobox>
</template>Custom Trigger
<script setup>
import { ref } from "vue";
import { PhCaretUpDown } from "@phosphor-icons/vue";
import { Button } from "@dicehub/phi/components/button";
import { Combobox, createComboboxCollection } from "@dicehub/phi/components/combobox";
const value = ref(["en"]);
const collection = createComboboxCollection({
items: [
{ emoji: "🇬🇧", label: "English", value: "en" },
{ emoji: "🇫🇷", label: "French", value: "fr" },
{ emoji: "🇩🇪", label: "German", value: "de" },
],
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
});
</script>
<template>
<Combobox v-model="value" :collection="collection">
<Combobox.Trigger as-child>
<Button variant="ghost" size="sm">
<Combobox.Value v-slot="{ items }" placeholder="Select language">
<span>{{ items[0]?.emoji }} {{ items[0]?.label }}</span>
</Combobox.Value>
<PhCaretUpDown :size="14" aria-hidden="true" />
</Button>
</Combobox.Trigger>
<Combobox.Content>
<Combobox.Input placeholder="Search languages" />
<Combobox.Empty />
<Combobox.List>
<template #default="{ item }">
<Combobox.Item :item="item">{{ item.emoji }} {{ item.label }}</Combobox.Item>
</template>
</Combobox.List>
</Combobox.Content>
</Combobox>
</template>Grouped
<script setup>
import { ref } from "vue";
import { Combobox, createComboboxCollection } from "@dicehub/phi/components/combobox";
const value = ref(["amsterdam"]);
const groups = [
{ label: "Asia", items: [{ label: "Tokyo", value: "tokyo" }] },
{ label: "Europe", items: [{ label: "Amsterdam", value: "amsterdam" }] },
];
const collection = createComboboxCollection({
items: groups.flatMap((group) => group.items),
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
});
</script>
<template>
<Combobox v-model="value" :collection="collection">
<Combobox.TriggerInput placeholder="Select server" />
<Combobox.Content>
<Combobox.List :render-items="false">
<Combobox.Group v-for="group in groups" :key="group.label">
<Combobox.GroupLabel>{{ group.label }}</Combobox.GroupLabel>
<Combobox.Item v-for="server in group.items" :key="server.value" :item="server">
{{ server.label }}
</Combobox.Item>
</Combobox.Group>
</Combobox.List>
</Combobox.Content>
</Combobox>
</template>Multiple
<script setup>
import { ref } from "vue";
import { Button } from "@dicehub/phi/components/button";
import { Combobox, createComboboxCollection } from "@dicehub/phi/components/combobox";
const value = ref([]);
const bots = [
{ label: "Googlebot", value: "googlebot", author: "Google" },
{ label: "Bingbot", value: "bingbot", author: "Microsoft" },
{ label: "DuckDuckBot", value: "duckduckbot", author: "DuckDuckGo" },
];
const collection = createComboboxCollection({
items: bots,
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
});
</script>
<template>
<div class="flex items-start gap-3">
<Combobox v-model="value" :collection="collection" multiple>
<Combobox.TriggerMultipleWithInput placeholder="Select bots" />
<Combobox.Content>
<Combobox.Empty />
<Combobox.List>
<template #default="{ item }">
<Combobox.Item :item="item">
<span>{{ item.label }}</span>
<span>{{ item.author }}</span>
</Combobox.Item>
</template>
</Combobox.List>
</Combobox.Content>
</Combobox>
<Button variant="primary">Submit</Button>
</div>
</template>With Field
<script setup>
import { ref } from "vue";
import { Combobox, createComboboxCollection } from "@dicehub/phi/components/combobox";
const value = ref([]);
const collection = createComboboxCollection({
items: [
{ label: "PostgreSQL", value: "postgresql" },
{ label: "MySQL", value: "mysql" },
{ label: "Redis", value: "redis" },
],
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
});
</script>
<template>
<Combobox
v-model="value"
:collection="collection"
label="Database"
description="Select your preferred database"
>
<Combobox.TriggerInput placeholder="Select database" />
<Combobox.Content>
<Combobox.Empty />
<Combobox.List>
<template #default="{ item }">
<Combobox.Item :item="item">{{ item.label }}</Combobox.Item>
</template>
</Combobox.List>
</Combobox.Content>
</Combobox>
</template>Disabled
<script setup>
import { Combobox, createComboboxCollection } from "@dicehub/phi/components/combobox";
const fruits = createComboboxCollection({
items: [{ label: "Apple", value: "apple" }, { label: "Banana", value: "banana" }],
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
});
const languages = createComboboxCollection({
items: [{ emoji: "🇬🇧", label: "English", value: "en" }, { emoji: "🇫🇷", label: "French", value: "fr" }],
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
});
</script>
<template>
<div class="flex flex-wrap gap-4">
<Combobox :collection="fruits" :default-value="['apple']" disabled>
<Combobox.TriggerInput placeholder="Select fruit" />
</Combobox>
<Combobox :collection="languages" :default-value="['en']" disabled>
<Combobox.TriggerValue />
</Combobox>
</div>
</template>Disabled Items
<script setup>
import { ref } from "vue";
import { Combobox, createComboboxCollection } from "@dicehub/phi/components/combobox";
const value = ref([]);
const databases = [
{ label: "PostgreSQL", value: "postgresql" },
{ label: "MySQL", value: "mysql" },
{ label: "MariaDB", value: "mariadb", disabled: true, note: "Beta" },
{ label: "Apache Cassandra", value: "cassandra", disabled: true, note: "Coming soon" },
];
const collection = createComboboxCollection({
items: databases,
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
isItemDisabled: (item) => item.disabled === true,
});
</script>
<template>
<Combobox v-model="value" :collection="collection">
<Combobox.TriggerInput placeholder="Select database" />
<Combobox.Content>
<Combobox.Empty />
<Combobox.List>
<template #default="{ item }">
<Combobox.Item :item="item">
{{ item.label }}<span v-if="item.note"> - {{ item.note }}</span>
</Combobox.Item>
</template>
</Combobox.List>
</Combobox.Content>
</Combobox>
</template>Error State
<script setup>
import { ref } from "vue";
import { Combobox, createComboboxCollection } from "@dicehub/phi/components/combobox";
const value = ref([]);
const collection = createComboboxCollection({
items: [
{ label: "PostgreSQL", value: "postgresql" },
{ label: "MySQL", value: "mysql" },
{ label: "Redis", value: "redis" },
],
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
});
</script>
<template>
<Combobox
v-model="value"
:collection="collection"
label="Database"
error="Please select a database."
>
<Combobox.TriggerInput placeholder="Select database" />
<Combobox.Content>
<Combobox.Empty />
<Combobox.List>
<template #default="{ item }">
<Combobox.Item :item="item">{{ item.label }}</Combobox.Item>
</template>
</Combobox.List>
</Combobox.Content>
</Combobox>
</template>Positioning and Dropdown Size
Pass Ark positioning options directly to Combobox.Content. Content values override matchingCombobox.Root positioning values. Use class or style for dimensions.
<script setup>
import { Combobox, createComboboxCollection } from "@dicehub/phi/components/combobox";
const collection = createComboboxCollection({
items: [
{ label: "Apple", value: "apple" },
{ label: "Apricot", value: "apricot" },
{ label: "Avocado", value: "avocado" },
{ label: "Banana", value: "banana" },
{ label: "Blueberry", value: "blueberry" },
],
itemToString: (item) => item.label,
itemToValue: (item) => item.value,
});
</script>
<template>
<Combobox :collection="collection">
<Combobox.TriggerInput placeholder="Select a fruit" />
<Combobox.Content
class="max-h-48"
placement="bottom-start"
strategy="fixed"
:flip="false"
:overflow-padding="12"
>
<Combobox.List>
<template #default="{ item }">
<Combobox.Item :item="item">{{ item.label }}</Combobox.Item>
</template>
</Combobox.List>
</Combobox.Content>
</Combobox>
</template>Sub-components
| Component | Description |
|---|---|
Combobox.Root | Manages value, filtering, open state, and field state. |
Combobox.TriggerInput | Searchable input trigger with clear and menu controls. Set as-child to forward input behavior to one custom control. |
Combobox.TriggerValue | Button-like value trigger. Set as-child to forward trigger behavior to one custom control. |
Combobox.TriggerMultipleWithInput | Multi-select trigger with removable chips and inline search. |
Combobox.Content | Positioned dropdown panel. Accepts all Ark positioning options. |
Combobox.Input | Search input flush with the dropdown top and side edges, with square bottom corners. |
Combobox.List | Listbox container. Can render collection items automatically. |
Combobox.Item | Selectable option with selected indicator. |
Combobox.Group | Groups related options. |
Combobox.GroupLabel | Accessible group heading. |
Combobox.Chip | Selected item chip used by the multi-select trigger. |
Combobox.Empty | Fallback content when the filtered collection is empty. |
API Reference
Combobox.Root
| Prop | Type | Default | Description |
|---|---|---|---|
collection | ListCollection | - | Ark collection used for labels, values, disabled items, and filtering. |
items | unknown[] | [] | Convenience input for creating a collection inside Phi. |
modelValue | string[] | - | Controlled selected values. Use v-model in Vue. |
defaultValue | string[] | [] | Initial selected values for uncontrolled usage. |
multiple | boolean | false | Allows more than one selected value. |
size | "xs" | "sm" | "base" | "lg" | "base" | Sets trigger height, padding, and font size. |
label | string | - | Optional field label. |
description | string | - | Optional helper text. |
error | string | - | Shows error text and marks the field invalid. |
disabled | boolean | false | Disables input and trigger interactions. |
closeOnSelect | boolean | true | Closes the dropdown after selecting a single item. |
positioning | ComboboxPositioningOptions | { placement: "bottom-start", gutter: 4 } | Base Ark positioning options. Direct Content props override matching values. |
Combobox.Item
| Prop | Type | Default | Description |
|---|---|---|---|
item | unknown | - | Collection item rendered as an option. |
value | unknown | - | Fallback item prop for simple values. |
Combobox.TriggerInput
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Merges Ark UI input behavior into the single default-slot child. |
default slot | component | - | Custom input control used when `asChild` is true. |
Combobox.TriggerValue
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Merges Ark UI trigger behavior into the single default-slot child. |
default slot | { items; label; value } | - | Value state for the default trigger or one custom trigger child. |
Combobox.Content
| Prop | Type | Default | Description |
|---|---|---|---|
class | string | - | Additional classes for dropdown width, height, and appearance. |
arrowPadding | number | Root value | Minimum space between an arrow and a content corner. |
boundary | Boundary | (() => Boundary) | "clipping-ancestors" | Root value | Boundary used to detect popup overflow. |
fitViewport | boolean | Root value | Limits the popup to the available viewport size. |
flip | boolean | Placement[] | Root value | Changes placement when the preferred side has no space. |
getAnchorElement | () => HTMLElement | VirtualElement | null | Root value | Returns a custom anchor element for the popup. |
getAnchorRect | (element) => AnchorRect | null | Root value | Deprecated anchor rectangle callback. Use getAnchorElement. |
gutter | number | 4 | Gap between the anchor and popup on the main axis. |
hideWhenDetached | boolean | Root value | Hides the popup when its anchor is detached. |
listeners | boolean | AutoUpdateOptions | Root value | Controls automatic position update listeners. |
offset | { mainAxis?: number; crossAxis?: number } | Root value | Adds main-axis and cross-axis offsets. |
onComplete | (data: ComputePositionReturn) => void | Root value | Runs after Floating UI computes a position. |
onPositioned | (data: { placed: boolean }) => void | Root value | Runs when the popup placement state changes. |
overflowPadding | number | Root value | Keeps this space between the popup and overflow boundary. |
overlap | boolean | Root value | Allows the popup to overlap its anchor. |
placement | Placement | "bottom-start" | Sets the preferred side and alignment. |
restoreStyles | boolean | Root value | Restores positioning styles during cleanup. |
sameWidth | boolean | Root value | Matches the popup width to the anchor width. |
shift | number | Root value | Moves the popup along the alignment axis when it overflows. |
sizeMiddleware | boolean | Root value | Controls Floating UI size calculations. |
slide | boolean | Root value | Slides the popup to keep it inside the boundary. |
strategy | "absolute" | "fixed" | Root value | Sets the CSS positioning strategy. |
updatePosition | ({ updatePosition, floatingElement }) => void | Root value | Receives a callback for manual position updates. |