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
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' | stringSelectVariant = 'inline' | 'field' | string
Minimal Composition
import {
Label,
Select,
SelectCombobox,
SelectHiddenSelect,
SelectIcon,
SelectListbox,
SelectOption,
SelectValue,
} from '@vega-ui/react';
import { useId } from 'react';
Required Parts
Select: root state and context providerSelectCombobox: trigger surfaceSelectValue: selected value or placeholderSelectListbox: options containerSelectOption: selectable option item
Optional Parts
SelectHiddenSelect: native form participationSelectIcon: visual affordance for the trigger
Composition Order
Typical composition:
SelectSelectHiddenSelectSelectComboboxSelectListboxSelectOption
Variants
- Size:
sm,md,lg - Variant:
field,inline - State variants: controlled value, controlled open state, disabled, read-only, searchable composition
Edge Cases
disabledblocks interaction completely;readOnlypreserves 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. SelectHiddenSelectis 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
PopoverorDialog, notSelect.
Common Mistakes
- Passing object values instead of
stringornumber. - Forgetting
SelectHiddenSelectin native form flows. - Leaving built-in typeahead enabled when embedding a searchable input into the listbox.
- Using
readOnlywhen 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
SelectOptionchildren - 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
SelectComboboxwhen 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:
SelectSelectHiddenSelectSelectComboboxSelectListboxSelectOption
Valid Composition Patterns
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}.