Select

Lets users select one option from a list. Ideal for forms, settings, or any interface where a single choice is required.

Example

Auto width
Matches the widest option by default, closer to a native select.
Value: No selection yet
Full width
Opt in with class="w-full" when you want the trigger to fill its container.
Value: strawberry-cheesecake

Custom Option Layout

Use #item-prefix and #item-label to tailor the standard row — for example, an avatar plus a two-line label with a secondary description. #prefix on the trigger reuses the selected option's accessory.

States

Disabled label option
Common sorting pattern where a disabled first option keeps an empty string value without breaking the select.
Value: Empty string

Trigger Slots

Prefix and suffix
Use the default trigger shell when you only need light customization.
Custom trigger
Replace the trigger content entirely when you need richer layout.

Label, Description, Error

Select supports label, description, error, and required directly — no FormControl wrapper needed. The error suppresses the description and wires aria-invalid + aria-errormessage onto the trigger.

We'll pick a default for you when you don't choose.

Notes

  • Prefer #item-prefix, #item-label, and #item-suffix when you want to customize the standard option row.
  • Use v-model:open when you need to control the menu state.
  • By default, Select sizes itself to fit its option content. Set class="w-full" when you want a full-width trigger.
  • Select accepts flat options only. Empty and nullish options are omitted.

API Reference

Show types
typescript
import type { Component } from 'vue'
import type { InputLabelingProps } from '../../composables/useInputLabeling'

export type SelectOptionValue = string | number | bigint | Record<string, any>

export type SelectOption =
  | string
  | {
      label: string
      value: SelectOptionValue
      disabled?: boolean
      icon?: string | Component
      description?: string
      slot?: string
      [key: string]: any
    }

export type SelectNormalizedOption = Exclude<SelectOption, string>

export interface SelectProps extends InputLabelingProps {
  /** Size of the select input. */
  size?: 'sm' | 'md' | 'lg' | 'xl'

  /** Visual style of the select input. */
  variant?: 'subtle' | 'outline' | 'ghost'

  /** Placeholder text displayed when no option is selected. */
  placeholder?: string

  /** If true, disables the select input. */
  disabled?: boolean

  /** The currently selected value. */
  modelValue?: SelectOptionValue

  /** Controls the visibility of the select menu. */
  open?: boolean

  /** Options to display in the dropdown. */
  options?: SelectOption[]

  /** Fallback empty-state copy rendered when no options are available. */
  emptyText?: string
}

export interface SelectTriggerSlotProps {
  /** Whether the select menu is currently open. */
  open: boolean

  /** Whether the trigger is disabled. */
  disabled: boolean

  /** Currently selected option, if any. */
  selectedOption: SelectNormalizedOption | null

  /** Plain-text label shown in the trigger. */
  displayValue: string
}

export interface SelectItemSlotProps {
  /** Option currently being rendered. */
  option: SelectNormalizedOption
}

export interface SelectSlots {
  /** Fully custom trigger renderer. */
  trigger?: (props: SelectTriggerSlotProps) => any

  /** Overrides the rendered label content. Receives `{ required }`. */
  label?: (props: { required: boolean }) => any

  /** Overrides the rendered description content. */
  description?: () => any

  /** Content rendered before the trigger value. */
  prefix?: () => any

  /** Content rendered after the trigger value. */
  suffix?: () => any

  /**
   * Shared renderer for option labels.
   * @deprecated use `#item-label` for per-row label customization. `#option` remains as a back-compat alias through v1.x.
   */
  option?: (props: SelectItemSlotProps) => any

  /** Content rendered before the standard option label. */
  'item-prefix'?: (props: SelectItemSlotProps) => any

  /** Content rendered for the standard option label area. */
  'item-label'?: (props: SelectItemSlotProps) => any

  /** Content rendered after the standard option label. */
  'item-suffix'?: (props: SelectItemSlotProps) => any

  /** Fallback content rendered when no options are available. */
  empty?: () => any

  /** Content rendered below the option list. */
  footer?: () => any

  [slotName: string]: ((props: any) => any) | undefined
}

export interface SelectEmits {
  /** Fired when the selected value changes. */
  'update:modelValue': [value: SelectOptionValue | undefined]

  /** Fired when the open state changes. */
  'update:open': [value: boolean]
}

export interface SelectExposed {}
Prop Default Type
size
"sm"
"md" | "sm" | "lg" | "xl"

Size of the select input.

variant
"subtle"
"subtle" | "outline" | "ghost"

Visual style of the select input.

placeholder
"Select option"
string

Placeholder text displayed when no option is selected.

disabled
boolean

If true, disables the select input.

modelValue
SelectOptionValue

The currently selected value.

open
false
boolean

Controls the visibility of the select menu.

options
[]
SelectOption[]

Options to display in the dropdown.

emptyText
"No options"
string

Fallback empty-state copy rendered when no options are available.

label
string

Label rendered above (or beside, for binary controls) the input.

description
string

Helper text rendered below the input. Hidden when `error` is set.

error
string | FrappeUIError

Error message rendered below the input. When set, the control receives `aria-invalid="true"` and `data-state="invalid"`. May be either a string or an `Error` object whose `messages?: string[]` is rendered as stacked lines (with `Error.message` as the fallback).

required
boolean

Marks the field as required. Renders an asterisk next to the label and forwards `required` / `aria-required` to the underlying control.

id
string

HTML id of the underlying control. Auto-generated via `useId()` if omitted.

Slot Payload
trigger
SelectTriggerSlotProps

Fully custom trigger renderer.

label
{ required: boolean; }

Overrides the rendered label content. Receives `{ required }`.

description

Overrides the rendered description content.

prefix

Content rendered before the trigger value.

suffix

Content rendered after the trigger value.

option

Deprecated — use `#item-label` for per-row label customization. `#option` remains as a back-compat alias through v1.x.

item-prefix
SelectItemSlotProps

Content rendered before the standard option label.

item-label
SelectItemSlotProps

Content rendered for the standard option label area.

item-suffix
SelectItemSlotProps

Content rendered after the standard option label.

empty

Fallback content rendered when no options are available.

footer

Content rendered below the option list.

Event Payload
update:modelValue
[value: SelectOptionValue | undefined]

Fired when the model value changes.

update:open
[value: boolean]

Fired when the open state changes.