<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";
const value = ref("apple");
</script>
<template>
<Select
v-model="value"
label="Favorite Fruit"
:items="{ apple: 'Apple', banana: 'Banana', cherry: 'Cherry' }"
/>
</template>Installation
Barrel
import { Select } from "@dicehub/phi";Granular
import { Select } from "@dicehub/phi/components/select";Usage
<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";
const value = ref("apple");
</script>
<template>
<Select
v-model="value"
label="Favorite Fruit"
:items="{ apple: 'Apple', banana: 'Banana', cherry: 'Cherry' }"
/>
</template>Examples
Basic
A select with a visible label. Providing label renders the label above the trigger.
<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";
const value = ref("apple");
</script>
<template>
<Select
v-model="value"
label="Favorite Fruit"
:items="{ apple: 'Apple', banana: 'Banana', cherry: 'Cherry' }"
/>
</template>Sizes
Use the size prop to match Input sizing (xs, sm, base, lg).
<script setup>
import { Select } from "@dicehub/phi/components/select";
</script>
<template>
<Select size="xs" aria-label="Select size xs" placeholder="Choose..." :items="{ a: 'Option A', b: 'Option B' }" />
<Select size="sm" aria-label="Select size sm" placeholder="Choose..." :items="{ a: 'Option A', b: 'Option B' }" />
<Select size="base" aria-label="Select size base" placeholder="Choose..." :items="{ a: 'Option A', b: 'Option B' }" />
<Select size="lg" aria-label="Select size lg" placeholder="Choose..." :items="{ a: 'Option A', b: 'Option B' }" />
</template>Without Visible Label
When a visible label is not needed, use aria-label for accessibility.
<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";
const value = ref("apple");
</script>
<template>
<Select
v-model="value"
aria-label="Select a fruit"
:items="{ apple: 'Apple', banana: 'Banana', cherry: 'Cherry' }"
/>
</template>With Description
Select shows helper text below the trigger through the description prop.
<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";
const value = ref(null);
</script>
<template>
<Select
v-model="value"
label="Issue Type"
description="Choose the category that best describes your issue"
:items="{ bug: 'Bug', documentation: 'Documentation', feature: 'Feature' }"
/>
</template>With Error
Pass error to display a validation message. The error replaces description text.
<script setup>
import { Select } from "@dicehub/phi/components/select";
</script>
<template>
<Select
label="Issue Type"
error="Please select an issue type"
:value="null"
:items="{ bug: 'Bug', documentation: 'Documentation', feature: 'Feature' }"
/>
</template>Placeholder
Use placeholder to show text when no value is selected.
<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";
const value = ref(null);
</script>
<template>
<Select
v-model="value"
label="Category"
placeholder="Choose a category..."
:items="{ bug: 'Bug', documentation: 'Documentation', feature: 'Feature' }"
/>
</template>Label with Tooltip
Add a tooltip icon next to the label with labelTooltip.
<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";
const value = ref(null);
</script>
<template>
<Select
v-model="value"
label="Priority"
label-tooltip="Higher priority issues are addressed first"
placeholder="Select priority"
:items="{ low: 'Low', medium: 'Medium', high: 'High', critical: 'Critical' }"
/>
</template>Custom Rendering
Use the value slot or renderValue to customize the selected value display.
<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";
const languages = [
{ value: "en", label: "English", emoji: "🇬🇧" },
{ value: "fr", label: "French", emoji: "🇫🇷" },
];
const value = ref(languages[0]);
const equalByValue = (item, value) => item?.value === value?.value;
</script>
<template>
<Select v-model="value" label="Language" :is-item-equal-to-value="equalByValue">
<template #value="{ value, empty }">
<span v-if="!empty">{{ value.emoji }} {{ value.label }}</span>
</template>
<Select.Option v-for="language in languages" :key="language.value" :value="language">
{{ language.emoji }} {{ language.label }}
</Select.Option>
</Select>
</template>Loading
The loading prop disables the trigger and swaps the selected value for a skeleton.
<script setup>
import { computed, onMounted, ref } from "vue";
import { Select } from "@dicehub/phi/components/select";
const loading = ref(true);
const value = ref(null);
const items = computed(() => loading.value ? undefined : { Visal: "Visal", John: "John" });
onMounted(() => window.setTimeout(() => { loading.value = false; }, 2000));
</script>
<template>
<Select aria-label="Loading select" loading />
<Select v-model="value" label="Assignee" :loading="loading" :items="items" />
</template>Multiple Selection
Enable multiple selection with multiple. The value becomes an array.
<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";
const value = ref(["Name", "Location", "Size"]);
const renderValue = (selected) => selected.join(", ");
</script>
<template>
<Select v-model="value" label="Visible Columns" multiple :render-value="renderValue">
<Select.Option value="Name">Name</Select.Option>
<Select.Option value="Location">Location</Select.Option>
<Select.Option value="Size">Size</Select.Option>
</Select>
</template>More Example
Object values can use isItemEqualToValue for value-based comparison.
<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";
const authors = [
{ id: 1, name: "John Doe", title: "Programmer" },
{ id: 2, name: "Alice Smith", title: "Software Engineer" },
];
const value = ref(null);
const equalById = (item, value) => item?.id === value?.id;
const renderAuthor = (author) => author?.name ?? "";
</script>
<template>
<Select
v-model="value"
label="Author"
placeholder="Select an author"
:is-item-equal-to-value="equalById"
:render-value="renderAuthor"
>
<Select.Option v-for="author in authors" :key="author.id" :value="author">
{{ author.name }} - {{ author.title }}
</Select.Option>
</Select>
</template>Disabled Options
Options can be disabled with the disabled prop.
<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";
const regions = [
{ value: "us-east", label: "US East" },
{ value: "eu-west", label: "EU West", disabled: true },
];
const value = ref(null);
const equalByValue = (item, value) => item?.value === value?.value;
</script>
<template>
<Select v-model="value" label="Deployment Region" :is-item-equal-to-value="equalByValue">
<Select.Option v-for="region in regions" :key="region.value" :value="region" :disabled="region.disabled">
{{ region.label }}
</Select.Option>
</Select>
</template>Disabled Items (via items prop)
The items object-map accepts descriptor objects with disabled metadata.
<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";
const value = ref("free");
</script>
<template>
<Select
v-model="value"
label="Plan"
:items="{
free: 'Free',
pro: 'Pro',
business: { label: 'Business', disabled: true },
enterprise: { label: 'Enterprise', disabled: true },
}"
/>
</template>Grouped Options
Use Select.Group, Select.GroupLabel, and Select.Separator to organize options.
<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";
const fruits = [{ value: "apple", label: "Apple" }];
const vegetables = [{ value: "carrot", label: "Carrot" }];
const value = ref(null);
const equalByValue = (item, value) => item?.value === value?.value;
</script>
<template>
<Select v-model="value" label="Food" :is-item-equal-to-value="equalByValue">
<Select.Group>
<Select.GroupLabel>Fruits</Select.GroupLabel>
<Select.Option v-for="food in fruits" :key="food.value" :value="food">{{ food.label }}</Select.Option>
</Select.Group>
<Select.Separator />
<Select.Group>
<Select.GroupLabel>Vegetables</Select.GroupLabel>
<Select.Option v-for="food in vegetables" :key="food.value" :value="food">{{ food.label }}</Select.Option>
</Select.Group>
</Select>
</template>Groups with Disabled Options
Combine groups, separators, and disabled options in one dropdown.
<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";
const available = [{ value: "us-east-1", label: "US East (N. Virginia)" }];
const unavailable = [{ value: "ap-south-1", label: "AP South (Mumbai)" }];
const value = ref(null);
const equalByValue = (item, value) => item?.value === value?.value;
</script>
<template>
<Select v-model="value" label="Server Region" :is-item-equal-to-value="equalByValue">
<Select.Group>
<Select.GroupLabel>Available</Select.GroupLabel>
<Select.Option v-for="region in available" :key="region.value" :value="region">{{ region.label }}</Select.Option>
</Select.Group>
<Select.Separator />
<Select.Group>
<Select.GroupLabel>Unavailable</Select.GroupLabel>
<Select.Option v-for="region in unavailable" :key="region.value" :value="region" disabled>
{{ region.label }}
</Select.Option>
</Select.Group>
</Select>
</template>Long List (Scrolling Test)
A long list validates popup scrolling without overscroll bounce.
<script setup>
import { ref } from "vue";
import { Select } from "@dicehub/phi/components/select";
const items = Array.from({ length: 50 }, (_, index) => ({
value: `item-${index + 1}`,
label: `Option ${index + 1}`,
}));
const value = ref(null);
const equalByValue = (item, value) => item?.value === value?.value;
</script>
<template>
<Select v-model="value" label="Long List Select" :is-item-equal-to-value="equalByValue">
<Select.Option v-for="item in items" :key="item.value" :value="item">
{{ item.label }}
</Select.Option>
</Select>
</template>API Reference
Select
Root select component with trigger, optional field label, and dropdown content. The trigger uses the control surface, including while open; the popup uses the base surface.
| Prop / Event | Type | Default | Description |
|---|---|---|---|
default slot | unknown | - | Child Select.Option, Select.Group, Select.GroupLabel, and Select.Separator components. If omitted, options are generated from items. |
trigger slot | { label; value; items; empty; open } | - | Replaces the trigger through Ark UI as-child forwarding. Render exactly one interactive child, such as Toolbar.Button. |
value slot | { value: unknown; items: SelectCollectionItem[]; empty: boolean } | - | Custom Vue slot for rendering the selected value inside the trigger. |
class | string | - | Additional CSS classes applied to the trigger. |
size | "xs" | "sm" | "base" | "lg" | "base" | Trigger size. Matches the Input component sizes. |
label | string | - | Visible label rendered above the trigger. |
hideLabel | boolean | false | Deprecated hidden-label mode. Prefer aria-label for selects without a visible label. |
placeholder | string | - | Text shown when no value is selected. |
loading | boolean | false | Shows a skeleton value and disables the trigger. |
disabled | boolean | false | Disables the select trigger and all selection interaction. |
required | boolean | - | Native required state. When explicitly false, the label shows optional text. |
labelTooltip | string | - | Tooltip content displayed next to the visible label. |
description | string | - | Helper text displayed below the trigger. |
error | string | SelectFieldError | - | Validation message. When present, it replaces description and marks the trigger invalid. |
items | Record<string, SelectItemValue> | SelectInputItem[] | - | Options rendered automatically when no default slot is provided. Object values may be descriptor objects with label and disabled. |
modelValue / v-model | unknown | unknown[] | null | - | Controlled selected value. Multiple mode uses an array. |
value | unknown | unknown[] | null | - | Controlled selected value alias for modelValue. |
defaultValue | unknown | unknown[] | null | - | Initial selected value for uncontrolled usage. |
multiple | boolean | false | Enables multiple selection and emits arrays from v-model and @value-change. |
renderValue | (value: unknown) => string | - | String formatter for the trigger value. Use the value slot for rich Vue markup. |
isItemEqualToValue | (itemValue: unknown, value: unknown) => boolean | Object.is | Custom comparison for object values. |
open | boolean | - | Controlled open state. |
defaultOpen | boolean | - | Initial open state for uncontrolled usage. |
positioning | object | bottom-start, 4px gutter | Ark positioning options for the popup. By default, the popup opens below the trigger and stays collision-aware. |
name | string | - | Native hidden select name for form submission. |
@update:model-value | (value: unknown) => void | - | v-model update event with the original option value. |
@value-change | (value: unknown, details: SelectValueChangeDetails) => void | - | Emitted when selection changes. Details include selected items and internal value keys. |
@open-change | (details: SelectOpenChangeDetails) => void | - | Emitted when the popup opens or closes. |
@highlight-change | (details: SelectHighlightChangeDetails) => void | - | Emitted when the highlighted option changes. |
Select.Option
Individual option rendered inside a Select list.
| Prop / Event | Type | Default | Description |
|---|---|---|---|
default slot | unknown | - | Visible option content. |
value | unknown | - | Option value emitted by the parent Select. |
label | unknown | - | Text label used for selection display when slot content is custom. |
disabled | boolean | false | Prevents this option from being selected. |
class | string | - | Additional CSS classes applied to the option item. |
Select.Group
Groups related options under a shared label.
| Prop / Event | Type | Default | Description |
|---|---|---|---|
default slot | unknown | - | Grouped options and an optional Select.GroupLabel. |
class | string | - | Additional CSS classes applied to the group wrapper. |
Select.GroupLabel
Visible label for a Select.Group.
| Prop / Event | Type | Default | Description |
|---|---|---|---|
default slot | unknown | - | Group heading content. |
class | string | - | Additional CSS classes applied to the group label. |
Select.Separator
Visual divider between option groups.
| Prop / Event | Type | Default | Description |
|---|---|---|---|
class | string | - | Additional CSS classes applied to the separator. |
Accessibility
Accessible Name
Use a visible label when possible. For compact controls without a visible label, provide aria-label or aria-labelledby.
Keyboard Navigation
Enter, Space, or ArrowDown opens the list. Arrow keys move highlight. Enter selects. Escape closes.
Screen Readers
The component is built on Ark UI Select primitives and preserves combobox/listbox semantics, disabled states, and hidden native form control output.