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-systemIn 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.
| Prop | Type | Default |
|---|---|---|
value | string | No default value |
defaultValue | string | No default value |
onValueChange | function | No default value |
open | boolean | No default value |
defaultOpen | boolean | No default value |
onOpenChange | function | No default value |
dir | enum | From ThemeProvider (overridable) |
name | string | No default value |
autoComplete | string | No default value |
disabled | boolean | No default value |
required | boolean | No default value |
form | string | No default value |
readOnly | boolean | No 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.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
onCloseAutoFocus | function | No default value |
onEscapeKeyDown | function | No default value |
onPointerDownOutside | function | No default value |
position | enum | popper (experience system; Radix default is item-aligned) |
side | enum | "bottom" |
sideOffset | number | 0 |
align | enum | center (experience system; Radix primitive default is start) |
alignOffset | number | 0 |
avoidCollisions | boolean | true |
collisionBoundary | Boundary | Boundary[] | [] |
collisionPadding | number | Padding | 10 |
arrowPadding | number | 0 |
sticky | enum | "partial" |
hideWhenDetached | boolean | false |
updatePositionStrategy | enum | No default value |
onPlaced | function | No default value |
| Data attribute | Values |
|---|---|
[data-state] | "open" | "closed" |
[data-side] | "left" | "right" | "bottom" | "top" |
[data-align] | "start" | "end" | "center" |
| CSS variable | Description |
|---|---|
--radix-select-content-transform-origin | The transform-origin computed from the content and trigger positions (when position="popper") |
--radix-select-content-available-width | Remaining width between the trigger and the boundary (when position="popper") |
--radix-select-content-available-height | Remaining height between the trigger and the boundary (when position="popper") |
--radix-select-trigger-width | Width of the trigger (when position="popper") |
--radix-select-trigger-height | Height 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.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
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.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
value | string | (required) |
disabled | boolean | No default value |
textValue | string | No default value |
| Data attribute | Values |
|---|---|
[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.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
Also accepts standard React div props.
SelectSeparator
Visual divider between items. Maps to SelectPrimitive.Separator.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
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.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
disabled | boolean | No default value |
size | enum | "md" (experience system: sm | md | lg) |
appearance | enum | "default" (experience system: default | filled) |
| Data attribute | Values |
|---|---|
[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.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
placeholder | ReactNode | No 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
| Key | Description |
|---|---|
Space | When focus is on SelectTrigger, opens the select and focuses the selected item. When focus is on an item, selects the focused item. |
Enter | When focus is on SelectTrigger, opens the select and focuses the first item. When focus is on an item, selects the focused item. |
ArrowDown | When focus is on SelectTrigger, opens the select. When focus is on an item, moves focus to the next item. |
ArrowUp | When focus is on SelectTrigger, opens the select. When focus is on an item, moves focus to the previous item. |
Esc | Closes 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.