Select

Pick one value from a list. By default the list opens with the selected option over the trigger.

WAI-ARIA pattern

Installation

npx nuecrea-ui add select

Usage

import * as Select from '@/components/ui/select';

function Option({ value, label, disabled }: { value: string; label: string; disabled?: boolean }) {
  return (
    <Select.Item value={value} disabled={disabled}>
      <Select.ItemText>{label}</Select.ItemText>
      <Select.ItemIndicator />
    </Select.Item>
  );
}

const frameworks = {
  react: 'React',
  angular: 'Angular',
  vue: 'Vue',
  svelte: 'Svelte',
  solid: 'Solid',
};
const sizes = { sm: 'Small', md: 'Medium', lg: 'Large' };

export default function SelectStory() {
  return (
    <main className="grid gap-8">
      <Select.Root name="framework" required items={frameworks}>
        <div className="grid gap-1.5">
          <Select.Label>Framework</Select.Label>
          <Select.Trigger>
            <Select.Value placeholder="Choose a framework" />
            <Select.Icon />
          </Select.Trigger>
        </div>
        <Select.Portal>
          <Select.Positioner>
            <Select.Popup>
              <Select.List>
                <Select.Group>
                  <Select.GroupLabel>Popular</Select.GroupLabel>
                  <Option value="react" label="React" />
                  <Option value="angular" label="Angular" />
                  <Option value="vue" label="Vue" disabled />
                </Select.Group>
                <Select.Separator />
                <Select.Group>
                  <Select.GroupLabel>Others</Select.GroupLabel>
                  <Option value="svelte" label="Svelte" />
                  <Option value="solid" label="Solid" />
                </Select.Group>
              </Select.List>
            </Select.Popup>
          </Select.Positioner>
        </Select.Portal>
      </Select.Root>

      <Select.Root defaultValue="md" items={sizes}>
        <div className="grid gap-1.5">
          <Select.Label>Size</Select.Label>
          <Select.Trigger>
            <Select.Value />
            <Select.Icon />
          </Select.Trigger>
        </div>
        <Select.Portal>
          <Select.Positioner>
            <Select.Popup>
              <Select.List>
                <Option value="sm" label="Small" />
                <Option value="md" label="Medium" />
                <Option value="lg" label="Large" />
              </Select.List>
            </Select.Popup>
          </Select.Positioner>
        </Select.Portal>
      </Select.Root>

      <Select.Root disabled defaultValue="eu" items={{ eu: 'Europe' }}>
        <div className="grid gap-1.5">
          <Select.Label>Region</Select.Label>
          <Select.Trigger>
            <Select.Value />
            <Select.Icon />
          </Select.Trigger>
        </div>
        <Select.Portal>
          <Select.Positioner>
            <Select.Popup>
              <Select.List>
                <Option value="eu" label="Europe" />
              </Select.List>
            </Select.Popup>
          </Select.Positioner>
        </Select.Portal>
      </Select.Root>
    </main>
  );
}

API reference

Select.Root

Owns value and open state; submits through a hidden input. Renders nothing.

PropTypeDefaultDescription
valueunknown—Controlled value.
defaultValueunknown—Initial value.
itemsRecord<string, string> | { label: string; value: unknown }[]—Labels for values, so the trigger can show a label before the list has opened.
namestring—Form field name of the hidden input.
disabledbooleanfalseDisabled.
readOnlybooleanfalseCan't change (aria-readonly, data-readonly).
requiredbooleanfalseRequired (aria-required).
modalbooleantrueLock page scroll while open.
EventPayloadDescription
onValueChangeunknownAngular: the `value` model's change event; also a ControlValueAccessor.
onOpenChangebooleanAngular: the `open` model's change event.

Select.Label

Labels the trigger. Renders <div>.

Select.Trigger

role=combobox. Arrows/Enter/Space open; typing selects without opening. Renders <button> with role combobox.

Data attributePresent when
data-popup-openopen
data-popup-sideopen: the side (bottom while aligned)
data-pressedopened by pointer
data-disableddisabled
data-placeholderno value

Select.Value

The selected label, or the placeholder. Renders <span>.

PropTypeDefaultDescription
placeholderstring—Shown with no value.
Data attributePresent when
data-placeholderno value

Select.Icon

Decorative (styled: up/down chevrons). Renders <span>.

Data attributePresent when
data-popup-openopen

Select.Portal

Moves the list to the end of <body>; stays mounted after first open. Renders <div>.

Select.Positioner

Positions the list; alignItemWithTrigger (default) puts the selected option over the trigger, falling back to anchoring near viewport edges. Renders <div> with role presentation.

PropTypeDefaultDescription
alignItemWithTriggerbooleantrueAlign the selected option with the trigger.
Data attributePresent when
data-openopen
data-closedclosed (the popup stays mounted after first open)
data-alignresolved alignment
data-side'none' while aligned with the trigger, else the resolved side

Select.List

role=listbox. Renders <div> with role listbox.

Select.Item

An option; disabled options are focusable but not selectable. Renders <div> with role option.

PropTypeDefaultDescription
valueunknown—The option's value.
disabledbooleanfalseNot selectable.
Data attributePresent when
data-selectedselected
data-highlightedhighlighted
data-disableddisabled

Select.ItemText

The option's label; aligned over the trigger's value. Renders <div>.

Select.ItemIndicator

Shown on the selected option (styled: a check). Renders <span>.

Data attributePresent when
data-selectedselected

Select.Group

Groups options. Renders <div> with role group.

Select.GroupLabel

Labels a group. Renders <div>.

Select.Separator

A divider (presentational). Renders <div> with role presentation.

Data attributePresent when
data-orientationalways: horizontal

Keyboard

KeyAction
ArrowDown / ArrowUp / Enter / SpaceOn the trigger: open on the selected option.
LettersOn the closed trigger: select the next matching option (no wrap). Open: highlight it.
ArrowDown / ArrowUpMove (no wrap); disabled options included.
Home / EndFirst / last option.
Enter / SpaceSelect and close.
Escape / TabClose; focus returns to the trigger.