Skip to content
v2.5.0
English
Русский
Documentation

Select

Select is a compound component built from a root state container plus exported child parts for trigger, value, options, and native form participation.
React
Vue
Svelte
select.tsx · 24 lines
import {
  Label,
  Select,
  SelectCombobox,
  SelectIcon,
  SelectListbox,
  SelectOption,
  SelectValue,
} from '@vega-ui/react';

<div>
  <Label id='preview-framework-label'>Framework</Label>
Browser support
Newly available 2025
  • Chrome 116
  • Edge 116
  • Firefox 125
  • Safari 17
  • Safari on iOS 18.3
Bundle size
Tested in
82 tests

Summary

Select is a compound component built from a root state container plus exported child parts for trigger, value, options, and native form participation. SelectListbox renders in the browser top layer via popover="manual", so it is never clipped by overflow or covered by a modal dialog. It is the field-oriented single-choice layer, not a generic floating-content primitive.

Imports

import {
  Select,
  SelectCombobox,
  SelectHiddenSelect,
  SelectIcon,
  SelectListbox,
  SelectOption,
  SelectValue,
  type SelectProps,
  type SelectSize,
  type SelectVariant,
} from '@vega-ui/react';

Exported Types

  • SelectProps<V extends string | number = string>
  • SelectSize = 'sm' | 'md' | 'lg' | string
  • SelectVariant = 'inline' | 'field' | string

Minimal Composition

minimal-composition.tsx · 34 lines
import {
  Label,
  Select,
  SelectCombobox,
  SelectHiddenSelect,
  SelectIcon,
  SelectListbox,
  SelectOption,
  SelectValue,
} from '@vega-ui/react';
import { useId } from 'react';

Required Parts

  • Select: root state and context provider
  • SelectCombobox: trigger surface
  • SelectValue: selected value or placeholder
  • SelectListbox: options container
  • SelectOption: selectable option item

Optional Parts

  • SelectHiddenSelect: native form participation
  • SelectIcon: visual affordance for the trigger

Composition Order

Typical composition:

  1. Select
  2. SelectHiddenSelect
  3. SelectCombobox
  4. SelectListbox
  5. SelectOption

Variants

Edge Cases

  • disabled blocks interaction completely; readOnly preserves focusability while preventing changes. See Disabled and Read-Only.
  • Filtering inside the listbox should usually disable built-in typeahead with typeahead={false} to avoid keyboard conflicts.
  • Values are typed as string | number; object values are not supported by the public API.
  • SelectHiddenSelect is recommended when the value should participate in native form participation.
  • If the product needs richer arbitrary overlay content rather than one scalar value, the correct abstraction is usually Popover or Dialog, not Select.

Common Mistakes

  • Passing object values instead of string or number.
  • Forgetting SelectHiddenSelect in native form flows.
  • Leaving built-in typeahead enabled when embedding a searchable input into the listbox.
  • Using readOnly when the intended behavior is a fully disabled control.

Examples

Basic

Basic: field variant with a visible label

A visible Label points at the SelectCombobox by id, which gives the trigger its accessible name. Options are rendered from data, and the placeholder shows until the user picks a team.

field-variant.tsx · 41 lines
import {
  Label,
  Select,
  SelectCombobox,
  SelectIcon,
  SelectListbox,
  SelectOption,
  SelectValue,
} from '@vega-ui/react';
import { useId } from 'react';

const teams = [

Controlled/Stateful

Controlled/Stateful: controlled value with clear action

The parent owns the priority and derives the response time from it. Clearing sets the value to null: undefined would switch the select to uncontrolled mode and keep the old selection.

controlled-value-with-clear-action.tsx · 56 lines
import {
  Button,
  Label,
  Select,
  SelectCombobox,
  SelectIcon,
  SelectListbox,
  SelectOption,
  SelectValue,
  Text,
} from '@vega-ui/react';
import { useId, useState } from 'react';

Controlled/Stateful: controlled open state

open and onChangeOpen let another control open the list. The button moves focus to the trigger first, so arrow keys and Enter work right away, and a hint is shown only while the list is open.

controlled-open-state.tsx · 59 lines
import {
  Button,
  Label,
  Select,
  SelectCombobox,
  SelectIcon,
  SelectListbox,
  SelectOption,
  SelectValue,
  Text,
} from '@vega-ui/react';
import { useId, useRef, useState } from 'react';

Form/Integration

Form/Integration: native form participation

SelectHiddenSelect with a name puts each value into FormData, so the submit handler reads the selects like native fields. Each trigger keeps its own Label.

native-form-participation.tsx · 68 lines
import {
  Button,
  Label,
  Select,
  SelectCombobox,
  SelectHiddenSelect,
  SelectIcon,
  SelectListbox,
  SelectOption,
  SelectValue,
  Text,
} from '@vega-ui/react';

Layout/Overlay

Layout/Overlay: inline variant inside sentence-like UI

variant='inline' makes the select part of a sentence. The surrounding text is not a label, so the trigger gets its own aria-label.

inline-variant-inside-sentence-like-ui.tsx · 31 lines
import {
  Select,
  SelectCombobox,
  SelectHiddenSelect,
  SelectIcon,
  SelectListbox,
  SelectOption,
  SelectValue,
  Text,
} from '@vega-ui/react';

export default function InlineVariantInsideSentenceLikeUi() {

Layout/Overlay: select inside dialog

The list opens in the top layer above the modal dialog, so no portal is needed. The form inside the dialog reads the value with FormData and closes the dialog on submit.

select-inside-dialog.tsx · 80 lines
import type { CSSProperties, FormEvent } from 'react';
import {
  Button,
  Dialog,
  DialogClose,
  DialogContent,
  DialogTrigger,
  Heading,
  Icon,
  IconButton,
  Label,
  Select,

Layout/Overlay: one select per table row

Each row has its own select that updates one member in the parent state. Every trigger is named after its row, so a screen reader announces "Role of Mei Tanaka" instead of a list of identical "Role" fields.

select-in-table-rows.tsx · 72 lines
import {
  Select,
  SelectCombobox,
  SelectIcon,
  SelectListbox,
  SelectOption,
  SelectValue,
  Table,
  TableBody,
  TableData,
  TableHead,
  TableHeading,

Error

Error: required choice with a linked message

The form checks the value on submit, marks the trigger with aria-invalid, links the message through aria-describedby and moves focus back to the field. Picking any option clears the error.

select-required-error.tsx · 80 lines
import {
  Button,
  HelperText,
  Label,
  Select,
  SelectCombobox,
  SelectHiddenSelect,
  SelectIcon,
  SelectListbox,
  SelectOption,
  SelectValue,
  Text,

Disabled

Disabled: inherited from a fieldset

The trigger is a native button, so a disabled Fieldset disables it and its hidden select together: the field cannot be opened and is left out of FormData. The switch toggles that state.

fully-disabled-field.tsx · 55 lines
import {
  Fieldset,
  Label,
  Select,
  SelectCombobox,
  SelectHiddenSelect,
  SelectIcon,
  SelectListbox,
  SelectOption,
  SelectValue,
  Switch,
  SwitchIndicator,

Disabled: read-only value with an explanation

readOnly keeps the trigger focusable and the value submitted, but the list does not open. The reason is linked to the trigger with aria-describedby.

read-only-field.tsx · 38 lines
import {
  HelperText,
  Label,
  Select,
  SelectCombobox,
  SelectHiddenSelect,
  SelectIcon,
  SelectListbox,
  SelectOption,
  SelectValue,
} from '@vega-ui/react';
import { useId } from 'react';

Edge

Edge: hundreds of options

288 time slots in five-minute steps. The list scrolls by itself, opens on the selected option, and typeahead jumps to a slot: type "14" to reach 14:00.

long-scrollable-option-list.tsx · 41 lines
import {
  Label,
  Select,
  SelectCombobox,
  SelectHiddenSelect,
  SelectIcon,
  SelectListbox,
  SelectOption,
  SelectValue,
} from '@vega-ui/react';
import { useId } from 'react';

Edge: short value text for long options

Without children SelectValue copies the selected option on the client. Children replace that copy, so long options can keep a short trigger, and they also render on the server; the placeholder still shows while nothing is selected.

custom-value-text.tsx · 28 lines
import {
  Select,
  SelectCombobox,
  SelectIcon,
  SelectListbox,
  SelectOption,
  SelectValue,
} from '@vega-ui/react';
import { useState } from 'react';

export default function CustomValueText() {
  const [currency, setCurrency] = useState<string | null>('EUR');

Edge: options loaded when the list opens

The projects are fetched on the first open, with aria-busy and a spinner while they load. The saved selection is not among the rendered options yet, so its name is passed to SelectValue as children.

async-options.tsx · 73 lines
import {
  Label,
  Select,
  SelectCombobox,
  SelectIcon,
  SelectListbox,
  SelectOption,
  SelectValue,
  Spinner,
  Text,
} from '@vega-ui/react';
import { useId, useState } from 'react';

Anatomy

Overview

Select is a compound component. The root owns open state, selection state, option registration, keyboard navigation, and typeahead behavior. The exported child parts consume that shared context.

Required Parts

Select

Root provider for the whole composition.

  • Required: yes
  • Owns: selected value, open state, active option, keyboard interactions
  • Must wrap every other Select* part

SelectCombobox

Visible trigger surface that opens and closes the listbox.

  • Required: yes
  • Typical children: SelectValue, SelectIcon
  • Should remain inside Select

SelectValue

Current value renderer or placeholder renderer.

  • Required: effectively yes in most UI
  • Accepts custom children when the selected value needs richer rendering
  • Should stay inside SelectCombobox

SelectIcon

Optional visual indicator for the trigger.

  • Required: no
  • Usually placed after SelectValue

SelectListbox

Container for selectable options.

  • Required: yes
  • Should contain one or more SelectOption children
  • Renders in the top layer via popover="manual", so no portal part is needed
  • Handles focus itself: arrow keys move focus through the options, and focus returns to SelectCombobox when the list closes

SelectOption

Selectable item registered into the root option model.

  • Required: yes
  • Must be rendered inside SelectListbox
  • Value type must match string | number

Optional Parts

SelectHiddenSelect

Hidden native select for form submission and browser form semantics.

  • Required: no
  • Recommended: yes for native forms
  • Omit only when the component is used outside normal form submission

SelectIcon

Optional visual indicator for the trigger.

  • Required: no
  • Usually placed after SelectValue

Composition Order

Typical composition:

  1. Select
  2. SelectHiddenSelect
  3. SelectCombobox
  4. SelectListbox
  5. SelectOption

Valid Composition Patterns

minimal-composition.tsx · 34 lines
import {
  Label,
  Select,
  SelectCombobox,
  SelectHiddenSelect,
  SelectIcon,
  SelectListbox,
  SelectOption,
  SelectValue,
} from '@vega-ui/react';
import { useId } from 'react';

Every part in its place: the Label names the trigger through its id, and SelectHiddenSelect with a name submits the value with the form. Leave out only the hidden select when the value never goes through a form.

Invalid Composition Patterns

SelectOption outside SelectListbox

This breaks option registration and keyboard navigation.

SelectValue outside SelectCombobox

This breaks the trigger composition contract and makes the selected value placement ambiguous.

Child parts outside Select

The public parts rely on root context. Rendering them outside Select breaks behavior.

Search input without disabling typeahead

If a text input is rendered inside SelectListbox, built-in typeahead should usually be disabled with typeahead={false}.