sc1m/design
ComponentsSelect

Select

Select component built on Base UI, styled with sc1m tokens.

StableBase UI

Live preview

Installation

pnpm dlx shadcn@latest add @sc1m/select

This also merges the token layer and installs src/lib/cn.ts plus @base-ui/react. For the manual route — prerequisites, components.json, and app-level setup — see Installation.

Usage

import {
  Select,
  SelectValue,
  SelectGroup,
  SelectGroupLabel,
  SelectTrigger,
  SelectContent,
  SelectItem,
} from "@/components/ui/select";

Composition

Every part is a separate export. Select owns state; the rest are presentational and must appear in this order.

Select├── SelectTrigger│   └── SelectValue└── SelectContent    └── SelectItem

Example

The preview above, with its source. This is the demo module verbatim, so the code and the thing it renders cannot drift apart.

<Select defaultValue="Banana">
  <SelectTrigger>
    <SelectValue placeholder="Select a fruit" />
  </SelectTrigger>
  <SelectContent>
    {fruits.map((fruit) => (
      <SelectItem key={fruit} value={fruit}>
        {fruit}
      </SelectItem>
    ))}
  </SelectContent>
</Select>

API reference

Each part forwards every prop of its Base UI counterpart; the tables list the ones declared on the part itself. See Base UI's reference for the inherited element props.

Select

PropTypeDefaultDescription
inputRefReact.Ref<HTMLInputElement>A ref to access the hidden input element.
namestringIdentifies the field when a form is submitted.
formstringIdentifies the form that owns the hidden input.
autoCompletestringProvides a hint to the browser for autofill.
idstringThe id of the Select.
requiredbooleanfalseWhether the user must choose a value before submitting a form.
readOnlybooleanfalseWhether the user should be unable to choose a different option from the select popup.
disabledbooleanfalseWhether the component should ignore user interaction.
multipleMultiplefalseWhether multiple items can be selected.
highlightItemOnHoverbooleantrueWhether moving the pointer over items should highlight them.
defaultOpenbooleanfalseWhether the select popup is initially open.
onOpenChange((open: boolean, eventDetails: SelectRootChangeEventDetails) => void)Event handler called when the select popup is opened or closed.
onOpenChangeComplete((open: boolean) => void)Event handler called after any animations complete when the select popup is opened or closed.
openbooleanWhether the select popup is currently open.
modalbooleantrueDetermines if the select enters a modal state when open.
actionsRefReact.RefObject<SelectRootActions | null>A ref to imperative actions.
itemsRecord<string, React.ReactNode> | ReadonlyArray<{ label: React.ReactNode; value: any; }> | ReadonlyArray<Group<any>>Data structure of the items rendered in the select popup.
itemToStringLabel((itemValue: Value) => string)When the item values are objects (<Select.Item value={object}>), this function converts the object value to a string representation for display in the trigger.
itemToStringValue((itemValue: Value) => string)When the item values are objects (<Select.Item value={object}>), this function converts the object value to a string representation for form submission.
isItemEqualToValue((itemValue: Value, value: Value) => boolean)Custom comparison logic used to determine if a select item value matches the current selected value.
defaultValueSelectValueType<Value, Multiple> | nullThe uncontrolled value of the select when it's initially rendered.
valueSelectValueType<Value, Multiple> | nullThe value of the select.
onValueChange((value: SelectValueType<Value, Multiple> | (Multiple extends true ? never : null), eventDetails: SelectRootChangeEventDetails) => void)Event handler called when the value of the select changes.

SelectValue

PropTypeDefaultDescription
placeholderReact.ReactNodeThe placeholder value to display when no value is selected.

SelectTrigger

PropTypeDefaultDescription
disabledbooleanWhether the component should ignore user interaction.

SelectContent

PropTypeDefaultDescription
side"top" | "right" | "bottom" | "left""bottom"Which side of the trigger the popup is placed on. Ignored while alignItemWithTrigger is on.
alignItemWithTriggerbooleantrueOverlaps the popup onto the trigger so the selected item's text lands on the trigger's value — native-select behaviour. Set false inside a CSS-transformed ancestor, where that overlap is measured wrong and the popup lands away from its trigger.
finalFocusboolean | React.RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | HTMLElement | null | void)Determines the element to focus when the select popup is closed.

SelectItem

PropTypeDefaultDescription
valueanynullA unique value that identifies this select item.
disabledbooleanfalseWhether the component should ignore user interaction.
labelstringSpecifies the text label to use when the item is matched during keyboard text navigation.

Accessibility

Behaviour, focus management, and ARIA wiring come from Base UI's Select — see its reference for the full keyboard map.

  • Keyboard focus draws a 2px ring in --ring, never removed — only shown on :focus-visible, so a pointer press does not paint one.
  • Disabled parts mute their contents rather than the element, keeping the focus ring at full strength where the part stays focusable.

Design tokens

The component reads semantic tokens only — no raw colour, radius, or duration appears in its source. Override the token, not the component.

TokenApplied to
--accentSelectItem
--brand-radius-mdSelectTrigger, SelectContent
--brand-radius-smSelectItem
--fgSelectTrigger, SelectContent, SelectItem
--fg-mutedSelectTrigger
--lineSelectTrigger, SelectContent
--ringSelectTrigger
--shadow-overlaySelectContent
--surfaceSelectItem
--surface-raisedSelectTrigger, SelectContent

Source

src/components/ui/select/select.tsx — no next/* imports, so it drops into any React 19 + Tailwind v4 app.