Components

Select

Single-value select with a combobox trigger, popup listbox, option collection, scroll controls, portal, and hidden form input.

When to Use

Use Select when the user chooses one value from a list and the choices should stay hidden until needed. Use RadioGroup when a short list should remain visible for easy comparison, and use Combobox when users need to type to filter or enter a value.

Features

  • Controlled and uncontrolled selected value.
  • Controlled and uncontrolled open state.
  • Keyboard navigation, typeahead search, highlighting, and selection.
  • Group, label, separator, viewport, scroll buttons, item text, and item indicator parts.
  • Hidden input for native form submission.
  • Stack-aware Escape dismissal when nested inside parent overlays.
  • Integrates with Field.Root for trigger labels, descriptions, disabled state, and required state.
  • Optional portal and popup arrow.

Import

import { Select } from "@flowstack-ui/atom";

Anatomy

<Select.Root>
  <Select.Trigger>
    <Select.Value />
    <Select.Icon />
  </Select.Trigger>
  <Select.Portal>
    <Select.Content>
      <Select.ScrollUpButton />
      <Select.Viewport>
        <Select.Group>
          <Select.Label />
          <Select.Item>
            <Select.ItemText />
            <Select.ItemIndicator />
          </Select.Item>
        </Select.Group>
        <Select.Separator />
      </Select.Viewport>
      <Select.ScrollDownButton />
      <Select.Arrow />
    </Select.Content>
  </Select.Portal>
</Select.Root>

<Select.Listbox />

API Reference

Root

Owns value, open state, item registration, form submission, and Field integration. Root renders no DOM wrapper except its hidden form input when name is provided.

PropTypeDefault
valuestring-
defaultValuestring-
onValueChange(value: string) => void-
openboolean-
defaultOpenbooleanfalse
onOpenChange(open: boolean) => void-
disabledbooleanfalse
requiredbooleanfalse
namestring-
formstring-

ARIA: Root renders no semantic element. Trigger and Content own the ARIA contract.

Data attributes: Root renders no wrapper and exposes none.

When used inside Field.Root, disabled and required default to the Field state unless explicitly provided on Select.Root.

Trigger

Combobox button that opens the listbox and owns keyboard interaction.

PropTypeDefault
ariaLabelstring-
asChildbooleanfalse
renderRenderProp-
ARIA attributeValues
role"combobox"
aria-haspopup"listbox"
aria-expandedCurrent open state
aria-controlsGenerated Content/Listbox ID
aria-activedescendantHighlighted Item ID while open
aria-labelExplicit native value or value from ariaLabel
aria-labelledbyExplicit IDs or inherited Field label ID
aria-describedbyExplicit IDs or inherited Field description/error IDs
aria-disabledtrue when disabled
aria-requiredtrue when required
Data attributeValues
[data-slot]"select-trigger"
[data-state]"open" | "closed"
[data-disabled]Present when disabled

When used inside Field.Root, Trigger uses the Field control ID and inherits aria-labelledby / aria-describedby from the Field label and visible description or error content. Explicit id, aria-label, aria-labelledby, and aria-describedby props override this wiring.

Typing a printable character while the trigger is focused opens the listbox and highlights the first enabled item whose label starts with the typed text.

Value

Displays the registered text for the selected Item, or the placeholder when no value is selected. It renders a span.

PropTypeDefault
placeholderReactNode-

ARIA: Value adds no ARIA attributes; Trigger owns the control's name and value relationship.

Data attributeValues
[data-slot]"select-value"
[data-placeholder]Present when no selected label is available

Icon

Provides a decorative span for a consumer-supplied trigger icon.

PropTypeDefault
Props: Icon has no Atom-owned behavior props and accepts native span
props.
ARIA attributeValues
aria-hiddenAlways true
Data attributeValues
[data-slot]"select-icon"

Portal

Moves Content to another container and tells Select that Content is already in a portal, preventing a second portal wrapper.

PropTypeDefault
containerHTMLElement | nulldocument.body after mount
disabledbooleanfalse

ARIA: Portal renders no wrapper and adds no ARIA attributes.

Data attributes: Portal renders no wrapper and exposes none.

Content

Public alias of Listbox that renders and positions the popup option container. It owns dismissal, initial highlighting, and focus-scope registration.

PropTypeDefault
disablePortalbooleanfalse
ariaLabelstring-
containerHTMLElement | nulldocument.body after mount
ARIA attributeValues
role"listbox"
aria-labelValue from ariaLabel when provided
Data attributeValues
[data-slot]"select-listbox"
[data-state]"open" while rendered
[data-positioned]Present after the first positioning frame

ScrollUpButton

Renders only while the Viewport can scroll upward. It scrolls the option area without entering the keyboard focus order.

Props: Accepts native button props and children; it has no additional Atom-owned behavior props.

ARIA attributeValues
aria-hiddenAlways true
Data attributeValues
[data-slot]"select-scroll-up-button"

Viewport

Provides the scroll container registered with both scroll buttons. It renders a div and accepts native div props.

ARIA: Viewport adds no roles or ARIA attributes.

Data attributeValues
[data-slot]"select-viewport"

Group

Wraps related Items with role="group" and connects them to its Label.

Props: Group has no Atom-owned behavior props and accepts native div props.

ARIA attributeValues
role"group"
aria-labelledbyGenerated Label ID
Data attributeValues
[data-slot]"select-group"

Label

Names its nearest Group through a generated or consumer-provided ID.

Props: Label has no Atom-owned behavior props and accepts native div props, including id.

ARIA: Label adds no ARIA attributes; Group references its ID.

Data attributeValues
[data-slot]"select-label"

Item

Registers one value and text label, owns pointer highlighting and selection, and returns focus to Trigger after selection.

PropTypeDefault
valuestringRequired
disabledbooleanfalse
labelstring-
ARIA attributeValues
role"option"
aria-selectedtrue when selected
aria-disabledtrue when disabled
aria-labelledbyItemText ID when ItemText is mounted
Data attributeValues
[data-slot]"select-item"
[data-state]"checked" | "unchecked"
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled
[data-value]Option value

ItemText

Registers the visible option text for Trigger display, typeahead, and Item's accessible name. It renders a span.

Props: ItemText has no Atom-owned behavior props and accepts native span props.

ARIA: ItemText adds no ARIA attributes; Item references its generated ID.

Data attributeValues
[data-slot]"select-item-text"

ItemIndicator

Renders a decorative selected marker only for the current Item unless forceMount keeps it mounted for stable composition.

PropTypeDefault
forceMountbooleanfalse
ARIA attributeValues
aria-hiddenAlways true
Data attributeValues
[data-slot]"select-item-indicator"
[data-state]"checked" | "unchecked"

Separator

Renders a horizontal separator between option sections.

Props: Separator has no Atom-owned behavior props and accepts native div props.

ARIA attributeValues
role"separator"
aria-orientation"horizontal"
Data attributeValues
[data-slot]"select-separator"

ScrollDownButton

Renders only while the Viewport can scroll downward. It scrolls the option area without entering the keyboard focus order.

Props: Accepts native button props and children; it has no additional Atom-owned behavior props.

ARIA attributeValues
aria-hiddenAlways true
Data attributeValues
[data-slot]"select-scroll-down-button"

Arrow

Provides a decorative span hook for a consumer-drawn popup arrow. Select does not calculate arrow geometry.

Props: Arrow has no Atom-owned behavior props and accepts native span props.

ARIA attributeValues
aria-hiddenAlways true
Data attributeValues
[data-slot]"select-arrow"

Listbox

Alternative public name for Content with the same positioned listbox behavior. Use either Content or Listbox, not both for the same popup.

PropTypeDefault
disablePortalbooleanfalse
ariaLabelstring-
containerHTMLElement | nulldocument.body after mount
ARIA attributeValues
role"listbox"
aria-labelValue from ariaLabel when provided
Data attributeValues
[data-slot]"select-listbox"
[data-state]"open" while rendered
[data-positioned]Present after the first positioning frame

Advanced compound parts can read useSelectContext, useSelectItemContext, or useSelectGroupContext. Their matching public providers expose the same contracts for low-level composition.

Examples

Form Select

import { Field, Select } from "@flowstack-ui/atom";

export default function PlanSelect() {
  return (
    <Field.Root id="plan" required>
      <Field.Label>Plan</Field.Label>
      <Select.Root name="plan" defaultValue="pro">
        <Select.Trigger>
          <Select.Value placeholder="Choose a plan" />
          <Select.Icon>Open</Select.Icon>
        </Select.Trigger>
        <Select.Content>
          <Select.Viewport>
            <Select.Item value="starter">
              <Select.ItemText>Starter</Select.ItemText>
            </Select.Item>
            <Select.Item value="pro">
              <Select.ItemText>Pro</Select.ItemText>
              <Select.ItemIndicator>Selected</Select.ItemIndicator>
            </Select.Item>
          </Select.Viewport>
        </Select.Content>
      </Select.Root>
    </Field.Root>
  );
}

Grouped Options

import { Select } from "@flowstack-ui/atom";

export default function GroupedSelect() {
  return (
    <Select.Root>
      <Select.Trigger ariaLabel="Choose a plan">
        <Select.Value placeholder="Choose a plan" />
      </Select.Trigger>
      <Select.Content>
        <Select.Group>
          <Select.Label>Plans</Select.Label>
          <Select.Item value="team">
            <Select.ItemText>Team</Select.ItemText>
          </Select.Item>
          <Select.Item value="enterprise">
            <Select.ItemText>Enterprise</Select.ItemText>
          </Select.Item>
        </Select.Group>
      </Select.Content>
    </Select.Root>
  );
}

Accessibility

Select follows the WAI-ARIA select-only combobox pattern: a button-based combobox controls a listbox, and Trigger references the highlighted option with aria-activedescendant. Provide a visible Field label, ariaLabel, or native aria-labelledby. Portalled Select content registers with a parent modal focus scope when opened inside Dialog, Drawer, or another modal primitive. Printable-character typeahead matches enabled option text; a single-character search cycles forward from the current matching option, while multi-character buffers match exact prefixes.

KeyDescription
ArrowDownOpens the listbox or moves to the next enabled item.
ArrowUpOpens the listbox from the last item or moves to the previous enabled item.
EnterOpens the listbox or selects the highlighted item.
SpaceOpens the listbox or selects the highlighted item.
HomeHighlights the first enabled item.
EndHighlights the last enabled item.
Printable characterTypeahead search.
EscapeCloses the listbox.
TabCloses the listbox and moves focus normally.

Changelog

See CHANGELOG.md.