Experience System
Components

Select

A styled single-choice listbox opened from a field-style trigger.

Installation

The component is exported from @by/experience-system. Add the package with your package manager:

pnpm add @by/experience-system

In this monorepo, depend on the workspace package (for example via workspace:* or your catalog) so imports resolve to packages/experience-system.

Composition

Use the following composition to build a Select:

Select
├── SelectTrigger
│   └── SelectValue
└── SelectContent
    ├── SelectGroup (optional)
    │   ├── SelectLabel (optional)
    │   └── SelectItem …
    ├── SelectSeparator (optional)
    └── SelectItem …

SelectContent includes portal (default container from ThemeProvider), viewport, and scroll affordances. Pair SelectTrigger with SelectValue for the closed-state label and placeholder.

Usage

import {
  Select,
  SelectTrigger,
  SelectValue,
  SelectContent,
  SelectItem,
} from '@by/experience-system';

Select and its parts are client components ('use client'). Use them inside a Client Component or a dynamic import when using the Next.js App Router.

<Select defaultValue="usa">
  <SelectTrigger aria-label="Country">
    <SelectValue placeholder="Select a country" />
  </SelectTrigger>
  <SelectContent>
    <SelectItem value="fr">France</SelectItem>
    <SelectItem value="es">Spain</SelectItem>
    <SelectItem value="usa">USA</SelectItem>
  </SelectContent>
</Select>

Pay attention:

Apply rtl:scale-x-[-1] to icons that imply direction inside the trigger so they mirror correctly in RTL layouts. The built-in chevrons already use this pattern.

When to use

  • Choosing one option from a moderate list where a native-like listbox pattern fits forms and filters.
  • When keyboard and screen reader behavior should match the Listbox pattern with a dedicated trigger.

When not to use

  • When the user must search or filter long lists—consider Combobox or another pattern.
  • For actions or navigation—prefer Dropdown Menu or visible links.

Examples

With groups

Use SelectGroup and SelectLabel to section options for scanning and accessible grouping.

With separators

Use SelectSeparator between related blocks of SelectItem rows.

API Reference

Subsection titles name @by/experience-system exports. Pieces map to Radix UI Select primitives (SelectPrimitive.*). The tables mirror the upstream @radix-ui/react-select prop surfaces (plus standard div / button / span attributes where the primitive extends Primitive.*). dir on the root defaults from ThemeProvider. SelectContent portals with container from ThemeProvider (via useContainerElement()), defaults position to popper and align to center, and always composes scroll buttons and a viewport around your items.

Select

Root. Contains all parts of the select. Exported as Select; forwards SelectPrimitive.Root props. readOnly is a Experience System extension: when true, onValueChange is not invoked and the selection cannot be changed via items.

PropTypeDefault
valuestringNo default value
defaultValuestringNo default value
onValueChangefunctionNo default value
openbooleanNo default value
defaultOpenbooleanNo default value
onOpenChangefunctionNo default value
direnumFrom ThemeProvider (overridable)
namestringNo default value
autoCompletestringNo default value
disabledbooleanNo default value
requiredbooleanNo default value
formstringNo default value
readOnlybooleanNo default value (experience system)

SelectContent

The surface that opens from the trigger. Wraps SelectPrimitive.Portal (with Experience System container) and SelectPrimitive.Content, and includes SelectScrollUpButton, SelectPrimitive.Viewport, and SelectScrollDownButton around children.

PropTypeDefault
asChildbooleanfalse
onCloseAutoFocusfunctionNo default value
onEscapeKeyDownfunctionNo default value
onPointerDownOutsidefunctionNo default value
positionenumpopper (experience system; Radix default is item-aligned)
sideenum"bottom"
sideOffsetnumber0
alignenumcenter (experience system; Radix primitive default is start)
alignOffsetnumber0
avoidCollisionsbooleantrue
collisionBoundaryBoundary | Boundary[][]
collisionPaddingnumber | Padding10
arrowPaddingnumber0
stickyenum"partial"
hideWhenDetachedbooleanfalse
updatePositionStrategyenumNo default value
onPlacedfunctionNo default value
Data attributeValues
[data-state]"open" | "closed"
[data-side]"left" | "right" | "bottom" | "top"
[data-align]"start" | "end" | "center"
CSS variableDescription
--radix-select-content-transform-originThe transform-origin computed from the content and trigger positions (when position="popper")
--radix-select-content-available-widthRemaining width between the trigger and the boundary (when position="popper")
--radix-select-content-available-heightRemaining height between the trigger and the boundary (when position="popper")
--radix-select-trigger-widthWidth of the trigger (when position="popper")
--radix-select-trigger-heightHeight of the trigger (when position="popper")

Also accepts standard React div props (for example className, style, id) where supported by the underlying Primitive.div.

SelectGroup

Groups items. Maps to SelectPrimitive.Group.

PropTypeDefault
asChildbooleanfalse

Also accepts standard React div props.

SelectItem

Selectable row. Maps to SelectPrimitive.Item. This experience system wraps children in SelectPrimitive.ItemText and renders a check ItemIndicator.

PropTypeDefault
asChildbooleanfalse
valuestring(required)
disabledbooleanNo default value
textValuestringNo default value
Data attributeValues
[data-state]"checked" | "unchecked"
[data-highlighted]Present when highlighted
[data-disabled]Present when disabled

Also accepts standard React div props. When Select is readOnly, items are forced non-interactive (disabled) by the experience system.

SelectLabel

Label for a group. Maps to SelectPrimitive.Label.

PropTypeDefault
asChildbooleanfalse

Also accepts standard React div props.

SelectSeparator

Visual divider between items. Maps to SelectPrimitive.Separator.

PropTypeDefault
asChildbooleanfalse

Also accepts standard React div props.

SelectTrigger

The button that toggles the select. Maps to SelectPrimitive.Trigger with field-control styling. A chevron icon is composed as SelectPrimitive.Icon.

PropTypeDefault
asChildbooleanfalse
disabledbooleanNo default value
sizeenum"md" (experience system: sm | md | lg)
appearanceenum"default" (experience system: default | filled)
Data attributeValues
[data-state]"open" | "closed"
[data-disabled]Present when disabled
[data-placeholder]Present when showing a placeholder
[data-readonly]Present when Select has readOnly (experience system)

Also accepts standard React button props (for example type, id, aria-label, aria-invalid for error styling).

SelectValue

Displays the selected item text. Maps to SelectPrimitive.Value.

PropTypeDefault
asChildbooleanfalse
placeholderReactNodeNo default value

Also accepts standard React span props except placeholder is typed as ReactNode (not the HTML attribute).

Accessibility

Follows the Listbox WAI-ARIA design pattern via Radix Select. See Radix accessibility for typeahead and labelling guidance. Give SelectTrigger an accessible name (aria-label or visible Label associated to the trigger id).

Keyboard interactions

KeyDescription
SpaceWhen focus is on SelectTrigger, opens the select and focuses the selected item. When focus is on an item, selects the focused item.
EnterWhen focus is on SelectTrigger, opens the select and focuses the first item. When focus is on an item, selects the focused item.
ArrowDownWhen focus is on SelectTrigger, opens the select. When focus is on an item, moves focus to the next item.
ArrowUpWhen focus is on SelectTrigger, opens the select. When focus is on an item, moves focus to the previous item.
EscCloses the select and moves focus to SelectTrigger.

Source in the repo: packages/experience-system/src/components/Select/Select.tsx. Agent-oriented contracts: packages/experience-system/src/components/Select/Select.instructions.md.