Components

Select

Source
Choose one value from a list presented in a mobile sheet.
Install with the CLI
npx @vyui/cli add select

Overview

VySelect combines the @vyui/core select and sheet primitives into a touch-oriented single-value picker. It supports primitive or object items, groups, labels, separators, disabled options, custom item slots, semantic trigger variants, and configurable sheet snap points.

Usage

<script setup lang="ts">
import { ref } from 'vue'
import { VySelect } from '@vyui/kit'

const value = ref<string>()
const items = ['Vue', 'React', 'Svelte']
</script>

<template>
  <VySelect
    v-model="value"
    :items="items"
    placeholder="Choose a framework"
  />
</template>

Item values are normalized to strings, and modelValue is always a string.

Object items and groups

Pass nested arrays to render multiple groups. Structural rows use type: 'label' or type: 'separator'.

<script setup lang="ts">
import { ref } from 'vue'
import { VySelect } from '@vyui/kit'

const value = ref('stockholm')
const items = [
  [
    { type: 'label', label: 'Europe' },
    { label: 'Stockholm', value: 'stockholm', icon: 'i-lucide-landmark' },
    { label: 'Lisbon', value: 'lisbon' },
  ],
  [
    { type: 'label', label: 'Americas' },
    { label: 'Toronto', value: 'toronto' },
    { label: 'New York', value: 'new-york', disabled: true },
  ],
]
</script>

<template>
  <VySelect v-model="value" :items="items" placeholder="Choose a city" />
</template>

Custom keys

Use valueKey and labelKey when the object shape comes from an API.

<script setup lang="ts">
import { ref } from 'vue'
import { VySelect } from '@vyui/kit'

const value = ref('pro')
const plans = [
  { id: 'free', name: 'Free' },
  { id: 'pro', name: 'Pro' },
]
</script>

<template>
  <VySelect
    v-model="value"
    :items="plans"
    value-key="id"
    label-key="name"
    placeholder="Choose a plan"
  />
</template>

Custom trigger and rows

<template>
  <VySelect v-model="value" :items="items" placeholder="Choose a person">
    <template #leading="{ iconColor }">
      <VyIcon name="i-lucide-user" :color="iconColor" />
    </template>

    <template #item-label="{ item }">
      {{ typeof item === 'object' ? item.label : item }}
    </template>

    <template #item-trailing="{ item }">
      <text v-if="typeof item === 'object' && item.meta" class="text-xs text-neutral-400">
        {{ item.meta }}
      </text>
    </template>
  </VySelect>
</template>

Features and behavior

  • Tapping the trigger opens a bottom sheet; tapping an enabled item selects it and closes the sheet.
  • modelValue controls selection. defaultValue initializes the core uncontrolled selection.
  • Primitive numbers and object values are converted with String().
  • Flat items render as one group. Nested arrays render separate groups.
  • placeholder appears in the empty trigger and as the sheet heading.
  • type: 'label' and type: 'separator' object items are structural and cannot be selected.
  • presentation="anchor" is accepted for API parity but currently still renders the sheet presentation.
  • The trigger's displayed selected label is derived from modelValue; bind v-model when the selected text must be shown reliably.

Select item

FieldTypeDefaultDescription
labelstringDisplay label when using the default labelKey.
valuestring | numberSelectable value when using the default valueKey; normalized to a string.
iconstringLeading Iconify icon for a selectable row.
type'item' | 'label' | 'separator''item'Selectable or structural row type.
disabledbooleanfalsePrevents a selectable row from being chosen.
[key: string]anyAdditional data available to item slots.

Props

PropTypeDefaultDescription
itemsSelectItemsundefinedFlat or grouped strings, numbers, and object items.
modelValuestringundefinedControlled selected value used by v-model.
defaultValuestringundefinedInitial value for uncontrolled core selection.
placeholderstringundefinedEmpty trigger text and sheet title.
disabledbooleanfalsePrevents the trigger from opening.
namestringundefinedName forwarded to the core form control.
colorColor'primary'Semantic trigger border and icon color.
variant'outline' | 'soft' | 'subtle' | 'ghost' | 'none''outline'Trigger surface style.
size'sm' | 'md' | 'lg' | 'xl''md'Trigger, row, text, and icon scale.
highlightbooleanfalseForces a static border using color.
trailingIconstringApp icon / i-lucide-chevron-downTrigger trailing Iconify name.
selectedIconstringApp icon / i-lucide-checkIcon shown on the selected row.
presentation'sheet' | 'anchor''sheet'Requested presentation; both values currently use the sheet.
snapPointsnumber[][0.5]Sheet snap fractions forwarded to SheetRoot.
handlebooleantrueShows the sheet drag handle.
valueKeystring'value'Object field read as the item value.
labelKeystring'label'Object field read as the item label.
classanyundefinedClasses merged onto the trigger surface.
uiPartial<Record<SelectSlot, any>>undefinedPer-instance theme slot overrides.

SelectItems is (SelectItem | string | number)[] or a nested array of those values. Color supports the default semantic registry and application extensions.

Emits

EventPayloadDescription
update:modelValuestringEmitted after an enabled item is selected.

The sheet open state is internal and is not emitted by the kit wrapper.

Slots

SlotPropsDescription
default{ modelValue?: string, open: boolean }Replaces the selected value or placeholder inside the trigger.
leading{ modelValue?: string, open: boolean, iconColor: string }Adds leading trigger content.
trailing{ modelValue?: string, open: boolean, iconColor: string }Replaces the trailing trigger icon.
item{ item, index }Replaces all built-in content inside a selectable row.
item-leading{ item, index }Replaces the built-in item icon.
item-label{ item, index }Replaces the item label.
item-trailing{ item, index }Adds content before the selected indicator.

index is the zero-based position within the item's group, not the flattened list.

Styling and theming

Override globally through appConfig.ui.select or locally with ui.

UI slotPurpose
baseTrigger surface, spacing, border, and disabled state.
value / placeholderSelected and empty trigger text.
leading / trailing / trailingIconTrigger accessory layout and built-in chevron.
content / handleSheet panel and drag handle.
sheetHeader / sheetTitlePlaceholder-derived sheet heading.
viewport / groupScrollable list and group layout.
label / separatorStructural item rows.
item / itemLabelSelectable row and text.
itemLeadingIconBuilt-in item icon.
itemTrailing / itemTrailingIconTrailing content and selected indicator.

The theme also defines reserved root, arrow, empty, avatar, and leading-icon slots that the current template does not render. color, variant, size, and highlight style the trigger; option rows remain neutral by default.

Accessibility

The core trigger exposes native button semantics and announces collapsed, expanded, or disabled state. Selectable rows expose option semantics, selected state, and disabled state. Keep the placeholder and custom trigger content descriptive, and ensure a custom item slot still includes a clear text label.

The sheet is optimized for touch selection. Test custom rows with VoiceOver and TalkBack, especially when visual metadata is the only distinction between options.

Platform notes

  • The list is rendered in a native bottom sheet with a dismissible backdrop and a vertically scrollable viewport.
  • The default [0.5] snap point opens the picker at half the viewport height.
  • Lynx SVG does not inherit currentColor; trigger slots receive a resolved iconColor for custom icons.
  • anchor presentation is not implemented yet and falls back to the same sheet behavior.
  • Empty object values become an empty string, which is not a valid selectable value in the core primitive. Ensure every selectable object has a populated valueKey.
  • Combobox for searchable selection.
  • DropdownMenu for action-oriented choices.
  • ActionSheet for actions that do not represent a form value.
  • The @vyui/core select primitives for custom presentations.