Select
A form control for choosing one or more predefined values from a popup list.
API Reference
Original primitive API
Behavior, accessibility details, and low-level props are documented by Base UI.
Choosing the right component
Use Select for strict, predefined choices where the trigger behaves like a button and users do
not type into the field itself.
- Choose
Selectfor canonical form pickers with fixed options. - Choose
Comboboxwhen users should search/filter predefined options via text input. - Choose
Autocompletewhen users may enter arbitrary values beyond suggested options.
Basic
import { Select, SelectField, SelectLabel, SelectTrigger, SelectValue, SelectIcon, SelectContent, SelectScrollUpArrow, SelectList, SelectItem, SelectItemIndicator, SelectItemText, SelectScrollDownArrow,} from "moduix";export function SelectDemo() { return ( <Select items={fruits}> <SelectField> <SelectLabel>Choose fruit</SelectLabel> <SelectTrigger className={styles.customTrigger}> <SelectValue placeholder="Select an option" /> <SelectIcon /> </SelectTrigger> </SelectField> <SelectContent> <SelectScrollUpArrow /> <SelectList> {fruits.map((item) => ( <SelectItem key={item.value} value={item.value}> <SelectItemIndicator /> <SelectItemText>{item.label}</SelectItemText> </SelectItem> ))} </SelectList> <SelectScrollDownArrow /> </SelectContent> </Select> );}const fruits = [ { label: "Apple", value: "apple" }, { label: "Banana", value: "banana" }, { label: "Blueberry", value: "blueberry" }, { label: "Grape", value: "grape" }, { label: "Kiwi", value: "kiwi" }, { label: "Mango", value: "mango" }, { label: "Orange", value: "orange" }, { label: "Pineapple", value: "pineapple" }, { label: "Strawberry", value: "strawberry" }, { label: "Watermelon", value: "watermelon" },];Full list of component variables available for project-level overrides.
| Property | Default | Description |
|---|---|---|
| --select-arrow-height | 0.625rem | Controls popup arrow height. |
| --select-arrow-inline-offset | 0.8125rem | Controls popup arrow inline offset. |
| --select-arrow-size | 0.5rem | Controls popup arrow side offset size. |
| --select-arrow-stroke-color | var(--select-popup-border-color) | Controls popup arrow stroke color. |
| --select-arrow-width | 1.25rem | Controls popup arrow width. |
| --select-backdrop-bg | var(--backdrop-bg) | Controls backdrop color. |
| --select-backdrop-blur | 2px | Controls backdrop blur. |
| --select-backdrop-transition | var(--transition-default) | Controls backdrop transition timing. |
| --select-bg | var(--color-background) | Controls trigger background. |
| --select-bg-active | var(--color-muted) | Controls trigger background when popup is open. |
| --select-bg-hover | var(--color-accent) | Controls trigger hover background. |
| --select-border-color | var(--color-border) | Controls trigger border color. |
| --select-border-width | var(--border-width-sm) | Controls trigger border width. |
| --select-color | var(--color-foreground) | Controls main text color. |
| --select-control-height | var(--size-lg) | Controls trigger minimum height. |
| --select-disabled-opacity | var(--opacity-disabled) | Controls disabled opacity. |
| --select-field-gap | 0.375rem | Controls field gap between label and trigger. |
| --select-focus-ring-color | var(--color-ring) | Controls focus ring color. |
| --select-focus-ring-offset | -1px | Controls focus ring offset. |
| --select-focus-ring-width | var(--select-border-width) | Controls focus ring width. |
| --select-group-label-bg | var(--select-popup-bg) | Controls group label background. |
| --select-group-label-color | var(--color-muted-foreground) | Controls group label color. |
| --select-group-label-font-size | var(--text-xs) | Controls group label font size. |
| --select-group-label-font-weight | var(--weight-semibold) | Controls group label font weight. |
| --select-group-label-line-height | var(--line-height-text-xs) | Controls group label line height. |
| --select-group-label-padding-bottom | 0.35rem | Controls group label bottom padding. |
| --select-group-label-padding-top | 0.35rem | Controls group label top padding. |
| --select-group-label-padding-x | 0.625rem | Controls group label horizontal padding. |
| --select-group-padding-bottom | var(--spacing-1) | Controls group bottom padding. |
| --select-highlight-bg | var(--color-foreground) | Controls highlighted item background. |
| --select-highlight-color | var(--color-background) | Controls highlighted item text color. |
| --select-highlight-inset-x | var(--spacing-1) | Controls highlighted item horizontal inset. |
| --select-highlight-radius | var(--radius-sm) | Controls highlighted item radius. |
| --select-icon-color | var(--color-muted-foreground) | Controls trigger icon color. |
| --select-icon-size | 0.875rem | Controls trigger icon wrapper size. |
| --select-icon-svg-size | 1rem | Controls trigger icon SVG size. |
| --select-item-color | var(--color-foreground) | Controls item text color. |
| --select-item-font-size | var(--text-sm) | Controls item font size. |
| --select-item-gap | 0.5rem | Controls item grid gap. |
| --select-item-indicator-icon-size | 0.75rem | Controls item indicator icon size. |
| --select-item-indicator-size | 0.75rem | Controls item indicator slot size. |
| --select-item-line-height | var(--line-height-text-sm) | Controls item line height. |
| --select-item-min-height | 2rem | Controls item minimum height. |
| --select-item-padding-x-end | 1rem | Controls item end padding. |
| --select-item-padding-x-start | 0.625rem | Controls item start padding. |
| --select-item-padding-y | 0.5rem | Controls item vertical padding. |
| --select-item-radius | 0 | Controls item radius. |
| --select-item-text-content-gap | var(--spacing-2) | Controls item text content gap. |
| --select-item-text-icon-color | currentColor | Controls item text icon color. |
| --select-item-text-icon-size | 1rem | Controls item text icon size. |
| --select-label-font-size | var(--text-sm) | Controls label font size. |
| --select-label-font-weight | var(--weight-medium) | Controls label font weight. |
| --select-label-line-height | var(--line-height-text-sm) | Controls label line height. |
| --select-list-max-height | var(--select-popup-max-height) | Controls list max height. |
| --select-list-padding-y | 0.25rem | Controls list vertical padding. |
| --select-list-scroll-padding-y | 0.25rem | Controls list scroll padding. |
| --select-overlap-offset | 1rem | Controls overlap offset for static side positioning. |
| --select-placeholder-color | var(--color-muted-foreground) | Controls placeholder color. |
| --select-popup-bg | var(--color-popover) | Controls popup background. |
| --select-popup-border-color | var(--color-border) | Controls popup border color. |
| --select-popup-border-width | var(--border-width-sm) | Controls popup border width. |
| --select-popup-max-height | 24rem | Controls popup max height. |
| --select-radius | var(--radius-md) | Controls trigger and popup radius. |
| --select-scroll-arrow-color | var(--select-color) | Controls scroll arrow color. |
| --select-scroll-arrow-height | 1rem | Controls scroll arrow height. |
| --select-scroll-arrow-icon-size | 0.875rem | Controls scroll arrow icon size. |
| --select-scroll-arrow-z-index | 1 | Controls scroll arrow stacking order. |
| --select-scale | var(--scale-popup) | Controls popup scale animation value. |
| --select-separator-color | var(--select-border-color) | Controls separator color. |
| --select-separator-margin-x | 1rem | Controls separator horizontal margin. |
| --select-separator-margin-y | 0.375rem | Controls separator vertical margin. |
| --select-separator-thickness | var(--border-width-sm) | Controls separator thickness. |
| --select-shadow | var(--shadow-lg) | Controls popup shadow. |
| --select-transition | var(--transition-default) | Controls popup transition timing. |
| --select-trigger-gap | 0.75rem | Controls trigger content gap. |
| --select-trigger-padding-x | 0.875rem | Controls trigger horizontal padding. |
| --select-width | 14rem | Controls trigger and popup anchor width. |
Interactive variables scoped for docs preview without changing size scale tokens.
| Property | Value | Default | Description |
|---|---|---|---|
| --select-bg | var(--color-background) | Controls trigger background. | |
| --select-bg-active | var(--color-muted) | Controls trigger background when open. | |
| --select-bg-hover | var(--color-accent) | Controls trigger background on hover. | |
| --select-border-color | var(--color-border) | Controls trigger border color. | |
| --select-color | var(--color-foreground) | Controls primary text color. | |
| --select-focus-ring-color | var(--color-ring) | Controls keyboard focus ring color. | |
| --select-highlight-bg | var(--color-foreground) | Controls highlighted item background. | |
| --select-highlight-color | var(--color-background) | Controls highlighted item text color. | |
| --select-popup-bg | var(--color-popover) | Controls popup background. | |
| --select-popup-border-color | var(--color-border) | Controls popup border color. | |
| --select-radius | var(--radius-md) | Controls trigger and popup radius. |
Anatomy
Select combines a trigger-like field with a popup list of options. Keep field parts grouped inside
SelectField and list parts inside SelectContent so labeling, selection state, and keyboard behavior
stay synchronized.
Select
├─ SelectField
│ ├─ SelectLabel
│ └─ SelectTrigger
│ ├─ SelectValue
│ └─ SelectIcon
└─ SelectContent
├─ service slots (internal): portal, backdrop, positioner, arrow
├─ SelectScrollUpArrow / SelectScrollDownArrow (optional)
└─ SelectList
└─ SelectItem[value]
├─ SelectItemIndicator
└─ SelectItemText<Select items={fruits}>
<SelectField>
<SelectLabel>Choose fruit</SelectLabel>
<SelectTrigger>
<SelectValue placeholder="Select an option" />
<SelectIcon />
</SelectTrigger>
</SelectField>
<SelectContent>
<SelectScrollUpArrow />
<SelectList>
{fruits.map((item) => (
<SelectItem key={item.value} value={item.value}>
<SelectItemIndicator />
<SelectItemText>{item.label}</SelectItemText>
</SelectItem>
))}
</SelectList>
<SelectScrollDownArrow />
</SelectContent>
</Select>| Part | Role |
|---|---|
Select | Root state machine. Controls selected value(s), popup open state, keyboard navigation, and item string mapping. |
SelectField | Field wrapper for label and trigger. Keeps form semantics and visible control layout together. |
SelectTrigger | Interactive button that opens/closes the popup and exposes open/disabled state attributes. |
SelectValue | Value renderer inside the trigger. Shows placeholder, selected label, or a custom function output. |
SelectContent | Popup surface that hosts list/scroll controls and configures internal service layers and positioning props. |
SelectItem | One selectable option. Works with SelectItemIndicator and SelectItemText to render selection state. |
In most cases, keep default popup infrastructure and style visible parts (SelectTrigger,
SelectValue, SelectContent, SelectItem, SelectItemIndicator, SelectItemText).
Use SelectContent service-slot props (classNames, slotProps, withBackdrop, arrow)
only when you need custom portal, layering, backdrop, or arrow behavior.
Composition
Use Select for root state and behavior props such as value, defaultValue, multiple,
onValueChange, items, itemToStringLabel, and itemToStringValue. Base UI root props pass
through as well: form props (name, form, required, inputRef, autoComplete), interaction
state (disabled, readOnly, open, defaultOpen, modal, onOpenChange,
onOpenChangeComplete, actionsRef), and item behavior (highlightItemOnHover,
isItemEqualToValue).
className styles the visible popup. classNames styles the internal service slots that are
hidden from the default composition. Use positioning props such as alignItemWithTrigger, side,
sideOffset, align, collisionPadding, and disableAnchorTracking directly on
SelectContent; use container for the portal target and slotProps for the matching Base UI
escape hatches:
<SelectContent
className={styles.popup}
withArrow
withBackdrop
slotProps={{
positioner: { sticky: true },
}}
classNames={{
portal: styles.portal,
backdrop: styles.backdrop,
positioner: styles.positioner,
arrow: styles.arrow,
}}
/>Examples
Indicator Right With Icon
Use indicator="end" to place the selected indicator after the item text. Pass children to SelectIcon or SelectItemTextIcon to use icons from your application.
import { Select, SelectField, SelectLabel, SelectTrigger, SelectValue, SelectIcon, ChevronDownIcon, SelectContent, SelectList, SelectItem, SelectItemText, SelectItemTextContent, SelectItemTextIcon, InfoIcon, SelectItemTextLabel, SelectItemIndicator,} from "moduix";export function IndicatorRightSelectDemo() { return ( <Select items={fruits}> <SelectField> <SelectLabel>Choose fruit</SelectLabel> <SelectTrigger> <SelectValue placeholder="Select an option" /> <SelectIcon> <ChevronDownIcon className={styles.customTriggerIcon} /> </SelectIcon> </SelectTrigger> </SelectField> <SelectContent> <SelectList> {fruits.map((item) => ( <SelectItem key={item.value} value={item.value} indicator="end"> <SelectItemText> <SelectItemTextContent> <SelectItemTextIcon> <InfoIcon className={styles.statusIcon} /> </SelectItemTextIcon> <SelectItemTextLabel>{item.label}</SelectItemTextLabel> </SelectItemTextContent> </SelectItemText> <SelectItemIndicator /> </SelectItem> ))} </SelectList> </SelectContent> </Select> );}.customTriggerIcon { color: var(--color-foreground);}.statusIcon { color: var(--color-muted-foreground);}const fruits = [ { label: "Apple", value: "apple" }, { label: "Banana", value: "banana" }, { label: "Mango", value: "mango" },];Grouped
Use SelectGroup and SelectGroupLabel to organize related items inside the popup.
import { Select, SelectField, SelectLabel, SelectTrigger, SelectValue, SelectIcon, SelectContent, SelectList, SelectGroup, SelectSeparator, SelectGroupLabel, SelectItem, SelectItemIndicator, SelectItemText,} from "moduix";export function GroupedSelectDemo() { return ( <Select> <SelectField> <SelectLabel>Choose produce</SelectLabel> <SelectTrigger> <SelectValue placeholder="Select item"> {(value) => typeof value === "string" ? groupedLabelByValue[value] ?? value : "Select item" } </SelectValue> <SelectIcon /> </SelectTrigger> </SelectField> <SelectContent> <SelectList> {groupedOptions.map((group, index) => ( <SelectGroup key={group.label}> {index > 0 ? <SelectSeparator /> : null} <SelectGroupLabel>{group.label}</SelectGroupLabel> {group.items.map((item) => ( <SelectItem key={item.value} value={item.value}> <SelectItemIndicator /> <SelectItemText>{item.label}</SelectItemText> </SelectItem> ))} </SelectGroup> ))} </SelectList> </SelectContent> </Select> );}const groupedOptions = [ { label: "Fruits", items: [ { label: "Apple", value: "apple" }, { label: "Mango", value: "mango" }, ], }, { label: "Vegetables", items: [ { label: "Carrot", value: "carrot" }, { label: "Spinach", value: "spinach" }, ], },];const groupedLabelByValue = Object.fromEntries( groupedOptions.flatMap((group) => group.items.map((item) => [item.value, item.label])),);Animated
Pass animation="scale" to SelectContent when the popup should animate in the default overlapping select positioning mode.
import { Select, SelectField, SelectLabel, SelectTrigger, SelectValue, SelectIcon, SelectContent, SelectList, SelectItem, SelectItemIndicator, SelectItemText,} from "moduix";export function AnimatedSelectDemo() { return ( <Select items={fruits}> <SelectField> <SelectLabel>Choose fruit</SelectLabel> <SelectTrigger> <SelectValue placeholder="Select an option" /> <SelectIcon /> </SelectTrigger> </SelectField> <SelectContent animation="scale" className={styles.animatedContent}> <SelectList> {fruits.map((item) => ( <SelectItem key={item.value} value={item.value}> <SelectItemIndicator /> <SelectItemText>{item.label}</SelectItemText> </SelectItem> ))} </SelectList> </SelectContent> </Select> );}.animatedContent { --select-transition: 180ms var(--ease-out); --select-scale: 0.96;}const fruits = [ { label: "Apple", value: "apple" }, { label: "Banana", value: "banana" }, { label: "Mango", value: "mango" },];Multiple
Use the multiple prop when a field can contain several selected values. Render SelectValue with a function to format the array.
import { Select, SelectField, SelectLabel, SelectTrigger, SelectValue, SelectIcon, SelectContent, SelectList, SelectItem, SelectItemIndicator, SelectItemText,} from "moduix";function renderMultipleValue(value) { if (value.length === 0) { return "Select languages"; } const first = languages[value[0]]; const suffix = value.length > 1 ? ` (+${value.length - 1})` : ""; return `${first}${suffix}`;}export function MultipleSelectDemo() { return ( <Select multiple defaultValue={["javascript", "typescript"]}> <SelectField> <SelectLabel>Languages</SelectLabel> <SelectTrigger> <SelectValue>{renderMultipleValue}</SelectValue> <SelectIcon /> </SelectTrigger> </SelectField> <SelectContent alignItemWithTrigger={false}> <SelectList> {languageValues.map((value) => ( <SelectItem key={value} value={value}> <SelectItemIndicator /> <SelectItemText>{languages[value]}</SelectItemText> </SelectItem> ))} </SelectList> </SelectContent> </Select> );}const languages = { javascript: "JavaScript", python: "Python", rust: "Rust", typescript: "TypeScript",};const languageValues = Object.keys(languages);Custom Styles
Use SelectContent props for popup infrastructure. Portal, Positioner, Backdrop, and Arrow are rendered internally, so application code only configures behavior and service-slot classes.
import { Select, SelectField, SelectLabel, SelectTrigger, SelectValue, SelectIcon, SelectContent, SelectList, SelectItem, SelectItemIndicator, SelectItemText,} from "moduix";export function CustomStylesSelectDemo() { return ( <Select items={fruits}> <SelectField> <SelectLabel>Choose fruit</SelectLabel> <SelectTrigger> <SelectValue placeholder="Select an option" /> <SelectIcon /> </SelectTrigger> </SelectField> <SelectContent alignItemWithTrigger={false} sideOffset={8} withArrow withBackdrop slotProps={{ positioner: { sticky: true }, }} className={styles.customPopup} classNames={{ portal: styles.customPortal, backdrop: styles.customBackdrop, positioner: styles.customPositioner, arrow: styles.customArrow, }} > <SelectList> {fruits.map((item) => ( <SelectItem key={item.value} value={item.value}> <SelectItemIndicator /> <SelectItemText>{item.label}</SelectItemText> </SelectItem> ))} </SelectList> </SelectContent> </Select> );}.customPortal { pointer-events: auto;}.customBackdrop { --select-backdrop-bg: rgb(15 23 42 / 0.48); --select-backdrop-blur: 3px;}.customPositioner { filter: drop-shadow(var(--shadow-md));}.customTrigger { position: relative; z-index: calc(var(--z-popup) + 1);}.customArrow { --select-arrow-stroke-color: var(--select-popup-border-color);}.customPopup { --select-radius: var(--radius-lg); --select-popup-bg: var(--color-background); --select-shadow: var(--shadow-xl);}const fruits = [ { label: "Apple", value: "apple" }, { label: "Banana", value: "banana" }, { label: "Mango", value: "mango" },];Controlled
Control value from React state when the selected option needs to coordinate with other application state.
import { Select, SelectField, SelectLabel, SelectTrigger, SelectValue, SelectIcon, SelectContent, SelectList, SelectItem, SelectItemIndicator, SelectItemText,} from "moduix";import { useState } from "react";export function ControlledSelectDemo() { const [value, setValue] = useState("light"); return ( <Select value={value} onValueChange={setValue} items={themeOptions}> <SelectField> <SelectLabel>Theme</SelectLabel> <SelectTrigger> <SelectValue placeholder="Select theme" /> <SelectIcon /> </SelectTrigger> </SelectField> <SelectContent> <SelectList> {themeOptions.map((item) => ( <SelectItem key={item.value} value={item.value}> <SelectItemIndicator /> <SelectItemText>{item.label}</SelectItemText> </SelectItem> ))} </SelectList> </SelectContent> </Select> );}const themeOptions = [ { label: "System", value: "system" }, { label: "Light", value: "light" }, { label: "Dark", value: "dark" },];Clearable With Null Item
Include an item with value={null} when users should be able to clear the field from the same popup.
import { Select, SelectField, SelectLabel, SelectTrigger, SelectValue, SelectIcon, SelectContent, SelectList, SelectItem, SelectItemIndicator, SelectItemText,} from "moduix";export function ClearableSelectDemo() { return ( <Select items={clearableThemeOptions}> <SelectField> <SelectLabel>Theme</SelectLabel> <SelectTrigger> <SelectValue /> <SelectIcon /> </SelectTrigger> </SelectField> <SelectContent> <SelectList> {clearableThemeOptions.map((item) => ( <SelectItem key={item.label} value={item.value}> <SelectItemIndicator /> <SelectItemText>{item.label}</SelectItemText> </SelectItem> ))} </SelectList> </SelectContent> </Select> );}const clearableThemeOptions = [ { label: "Select theme", value: null }, { label: "System", value: "system" }, { label: "Light", value: "light" }, { label: "Dark", value: "dark" },];Object Values
Use itemToStringLabel and itemToStringValue when option values are objects.
import { Select, SelectField, SelectLabel, SelectTrigger, SelectValue, SelectIcon, SelectContent, SelectList, SelectItem, SelectItemIndicator, SelectItemText,} from "moduix";export function ObjectValuesSelectDemo() { return ( <Select items={assignees.map((assignee) => ({ value: assignee, label: assignee.name, }))} itemToStringLabel={(assignee) => assignee.name} itemToStringValue={(assignee) => assignee.id} > <SelectField> <SelectLabel>Assignee</SelectLabel> <SelectTrigger> <SelectValue placeholder="Select assignee" /> <SelectIcon /> </SelectTrigger> </SelectField> <SelectContent> <SelectList> {assignees.map((assignee) => ( <SelectItem key={assignee.id} value={assignee}> <SelectItemIndicator /> <SelectItemText> <span className={styles.assigneeItemText}> <span className={styles.assigneeName}>{assignee.name}</span> <span className={styles.assigneeRole}>{assignee.role}</span> </span> </SelectItemText> </SelectItem> ))} </SelectList> </SelectContent> </Select> );}.assigneeItemText { display: flex; min-width: 0; flex-direction: column; gap: var(--spacing-1);}.assigneeName { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: var(--weight-semibold);}.assigneeRole { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--color-muted-foreground); font-size: var(--text-xs); line-height: var(--line-height-text-xs);}const assignees = [ { id: "u-1", name: "Leslie Alexander", role: "Product Manager" }, { id: "u-2", name: "Kathryn Murphy", role: "Marketing Lead" }, { id: "u-3", name: "Courtney Henry", role: "Design Systems" }, { id: "u-4", name: "Michael Foster", role: "Frontend Engineer" },];