Select
A single-choice dropdown, the user picks one option from a list. It supports optional search for long lists, binds the chosen value with v-model, and is built on Headless UI's listbox so keyboard navigation and accessibility come for free.
Reach for Select when the user picks one value from a defined set. Use MultiSelect when they can choose several, and Dropdown for a menu of actions rather than choosing a value.
Anatomy
Select ← root: holds v-model, provides context, sets placement
├── SelectTrigger ← the field-like button showing the current selection
└── SelectContent ← the floating panel (matches the trigger's width)
├── SelectSearch ← optional search input (needs `searchable` on the root)
└── SelectItem ← one per option; its content is the labelEach option is a SelectItem with a value and a label. The label is the display text the trigger shows once that option is selected; you can also put richer content inside the item for the dropdown row.
Usage
Give each SelectItem a value and a label.
<script setup lang="ts">
import { ref } from "vue";
import { Select, SelectTrigger, SelectContent, SelectItem } from "@dlbcodes/ui";
// Nothing selected yet — the trigger shows its placeholder.
const value = ref("");
</script>
<template>
<div class="w-full max-w-xs">
<Select v-model="value">
<SelectTrigger placeholder="Pick a framework" />
<SelectContent>
<SelectItem value="vue" label="Vue">Vue</SelectItem>
<SelectItem value="react" label="React">React</SelectItem>
<SelectItem value="svelte" label="Svelte">Svelte</SelectItem>
<SelectItem value="angular" label="Angular">Angular</SelectItem>
</SelectContent>
</Select>
</div>
</template>
Pre-filled value
Because each item carries its label as a prop, a select that's already bound to a value displays the correct label immediately, before the user ever opens it.
<script setup lang="ts">
import { ref } from "vue";
import { Select, SelectTrigger, SelectContent, SelectItem } from "@dlbcodes/ui";
// Bound to a value before the user opens the select. Because each item carries
// its `label`, the trigger resolves "React" on load — no opening required.
const value = ref("react");
</script>
<template>
<div class="w-full max-w-xs">
<Select v-model="value">
<SelectTrigger placeholder="Pick a framework" />
<SelectContent>
<SelectItem value="vue" label="Vue">Vue</SelectItem>
<SelectItem value="react" label="React">React</SelectItem>
<SelectItem value="svelte" label="Svelte">Svelte</SelectItem>
<SelectItem value="angular" label="Angular">Angular</SelectItem>
</SelectContent>
</Select>
</div>
</template>
Searchable
For long lists, add searchable to the root and include a SelectSearch. Items filter themselves by their label as the user types.
<script setup lang="ts">
import { ref } from "vue";
import { Select, SelectTrigger, SelectContent, SelectItem } from "@dlbcodes/ui";
const value = ref("");
</script>
<template>
<div class="w-full max-w-xs">
<Select v-model="value" searchable>
<SelectTrigger placeholder="Pick a country" />
<SelectContent>
<SelectSearch placeholder="Search countries…" />
<SelectItem value="pt">Portugal</SelectItem>
<SelectItem value="es">Spain</SelectItem>
<SelectItem value="fr">France</SelectItem>
<SelectItem value="de">Germany</SelectItem>
<SelectItem value="it">Italy</SelectItem>
<SelectItem value="jp">Japan</SelectItem>
<SelectItem value="br">Brazil</SelectItem>
<SelectItem value="ca">Canada</SelectItem>
<SelectItem value="cn">China</SelectItem>
<SelectItem value="us">United States</SelectItem>
</SelectContent>
</Select>
</div>
</template>
Rich items
The item's slot can hold more than text, an icon, a color dot, a formatted row. The label prop stays the source of truth for the trigger's display text, while the slot controls how the option looks in the list.
<script setup lang="ts">
import { ref } from "vue";
import { Select, SelectTrigger, SelectContent, SelectItem } from "@dlbcodes/ui";
import { PhCircle } from "@phosphor-icons/vue";
const value = ref("");
</script>
<template>
<div class="w-full max-w-xs">
<Select v-model="value">
<SelectTrigger placeholder="Set status" />
<SelectContent>
<SelectItem value="active">
<span class="flex items-center gap-2">
<PhCircle
class="size-3 text-success-text"
weight="fill"
/>
Active
</span>
</SelectItem>
<SelectItem value="away">
<span class="flex items-center gap-2">
<PhCircle
class="size-3 text-warning-text"
weight="fill"
/>
Away
</span>
</SelectItem>
<SelectItem value="offline">
<span class="flex items-center gap-2">
<PhCircle
class="size-3 text-text-tertiary"
weight="fill"
/>
Offline
</span>
</SelectItem>
</SelectContent>
</Select>
</div>
</template>
Disabled options
Mark an item disabled to make it non-selectable, it's skipped by keyboard navigation and can't be chosen.
<script setup lang="ts">
import { ref } from "vue";
import { Select, SelectTrigger, SelectContent, SelectItem } from "@dlbcodes/ui";
const value = ref("");
</script>
<template>
<div class="w-full max-w-xs">
<Select v-model="value">
<SelectTrigger placeholder="Choose a plan" />
<SelectContent>
<SelectItem value="free">Free</SelectItem>
<SelectItem value="pro">Pro</SelectItem>
<SelectItem value="enterprise" disabled>
Enterprise (contact sales)
</SelectItem>
</SelectContent>
</Select>
</div>
</template>
Inside a Field
Wrap it in a Field for a label and description with the accessibility wiring, just like the other form controls.
<script setup lang="ts">
import { ref } from "vue";
import {
Select,
SelectTrigger,
SelectContent,
SelectItem,
Field,
FieldLabel,
FieldContent,
FieldDescription,
} from "@dlbcodes/ui";
const role = ref("");
</script>
<template>
<Field class="w-full max-w-xs">
<FieldLabel>Role</FieldLabel>
<FieldContent>
<Select v-model="role">
<SelectTrigger placeholder="Assign a role" />
<SelectContent>
<SelectItem value="viewer">Viewer</SelectItem>
<SelectItem value="editor">Editor</SelectItem>
<SelectItem value="admin">Admin</SelectItem>
</SelectContent>
</Select>
<FieldDescription
>Controls what this member can do.</FieldDescription
>
</FieldContent>
</Field>
</template>
Props
Select
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | string | none | The selected value. Use with v-model. Required. |
searchable | boolean | false | Enables filtering (include a SelectSearch). |
placement | "bottom-start" | "bottom-end" | "bottom" | "top-start" | "top-end" | "top" | "bottom-start" | Where the panel opens relative to the trigger. |
offset | number | 4 | Gap (px) between trigger and panel. |
class | string | none | Classes merged onto the root. |
SelectTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
placeholder | string | "Select an option" | Shown when nothing is selected. |
variant | "primary" | "contrast" | "primary" | Field style (shares Input's variants). |
size | "sm" | "base" | "lg" | "xl" | "base" | Field size (shares Input's sizes). Heights are 32, 40, 48 and 56px, the same as Button and Input. |
class | string | none | Classes merged onto the trigger. |
SelectTrigger exposes selected (the value) and label (the display text) via its default slot, so you can render the selection however you like.
SelectContent
| Prop | Type | Default | Description |
|---|---|---|---|
width | "fit" | "full" | "3xs" | "2xs" | "xs" | "sm" | "md" | "lg" | "full" | Panel width. full matches the trigger; a token gives a fixed width. |
class | string | none | Classes merged onto the panel. |
SelectSearch
| Prop | Type | Default | Description |
|---|---|---|---|
placeholder | string | "Search..." | Search input placeholder. Requires searchable. |
SelectItem
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | none | The option's value (what v-model receives). Required. |
label | string | none | Display text shown in the trigger when selected. Falls back to the slot's text if omitted. |
disabled | boolean | false | Makes the option non-selectable and skipped by keyboard nav. |
Provide label so the trigger displays correctly, including for a pre-filled value. The item's slot controls how the option appears in the list (and is used as the label if label is omitted). The slot exposes active and selected for custom rendering.
Accessibility
- Built on Headless UI's
Listbox, so arrow-key navigation, type-ahead,Enterto select,Escapeto close, androle="listbox"/optionwiring are handled. - The selected option shows a check and is marked selected for assistive tech.
- Disabled options are skipped by keyboard navigation.
- Wrap in a
Field(or pair with aLabel) so the control has an accessible name.