1
ComponentsForm Controls

Select

A small known set of options for single or multiple selection without text search.

Purpose

Use Select for a bounded, known set of choices such as status, category, framework or filter values.

Status select

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

export function Example() {
  return (
    <Select defaultValue="active">
      <SelectTrigger aria-label="Status"><SelectValue /></SelectTrigger>
      <SelectPopup><SelectItem value="active">Active</SelectItem></SelectPopup>
    </Select>
  );
}

Installation

With the Siteplane registry alias configured, add the component and import the installed source from your app.

CLI

pnpm dlx shadcn@4.16.1 add @siteplane/select

Import

import {
  Select,
  SelectButton,
  SelectGroup,
  SelectGroupLabel,
  SelectItem,
  SelectLabel,
  SelectPopup,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";

Need the machine-readable source metadata? View registry JSON.

Usage

Use Select for choosing from a small known set. Use Combobox when the set needs filtering.

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

export function Example() {
  return (
    <Select defaultValue="active">
      <SelectTrigger aria-label="Status"><SelectValue /></SelectTrigger>
      <SelectPopup><SelectItem value="active">Active</SelectItem></SelectPopup>
    </Select>
  );
}

Examples

Groups, trigger icons, multiple selection, sizes, states and form integration are props and compositions of the same primitive.

Grouped options

<Select items={[...frontendFrameworks, ...backendFrameworks]}>
  <SelectTrigger aria-label="Select framework">
    <SelectValue placeholder="Select framework" />
  </SelectTrigger>
  <SelectPopup>
    <SelectGroup>
      <SelectGroupLabel>Frontend</SelectGroupLabel>
      {frontendFrameworks.map(({ label, value }) => (
        <SelectItem key={value} value={value}>{label}</SelectItem>
      ))}
    </SelectGroup>
    <SelectSeparator />
    <SelectGroup>
      <SelectGroupLabel>Backend</SelectGroupLabel>
      {backendFrameworks.map(({ label, value }) => (
        <SelectItem key={value} value={value}>{label}</SelectItem>
      ))}
    </SelectGroup>
  </SelectPopup>
</Select>

With icon

<Select defaultValue="next" items={frameworks}>
  <SelectTrigger aria-label="Select framework">
    <CableIcon aria-hidden="true" />
    <SelectValue />
  </SelectTrigger>
  <SelectPopup>
    {frameworks.map(({ label, value }) => (
      <SelectItem key={value} value={value}>{label}</SelectItem>
    ))}
  </SelectPopup>
</Select>

Multiple selection

function renderLanguages(value: Language[]) {
  if (value.length === 0) return "Select languages...";
  const first = value[0] ? languages[value[0]] : "";
  return first + (value.length > 1 ? ` (+${value.length - 1} more)` : "");
}

<Select defaultValue={["javascript", "typescript"]} multiple>
  <SelectTrigger aria-label="Select languages">
    <SelectValue>{renderLanguages}</SelectValue>
  </SelectTrigger>
  {/* alignItemWithTrigger={false} opts out of aligning the selected
      item with the trigger and uses plain dropdown positioning. */}
  <SelectPopup alignItemWithTrigger={false}>
    {languageValues.map((value) => (
      <SelectItem key={value} value={value}>{languages[value]}</SelectItem>
    ))}
  </SelectPopup>
</Select>

Sizes

<SelectTrigger size="sm">...</SelectTrigger>
<SelectTrigger>...</SelectTrigger>
<SelectTrigger size="lg">...</SelectTrigger>
<SelectTrigger size="xl">...</SelectTrigger>

States

{/* Placeholder */}
<SelectTrigger aria-label="Select framework">
  <SelectValue placeholder="Select framework" />
</SelectTrigger>

{/* Disabled */}
<SelectTrigger disabled>
  <SelectValue placeholder="Select framework" />
</SelectTrigger>

{/* Invalid */}
<SelectTrigger aria-invalid>
  <SelectValue placeholder="Select framework" />
</SelectTrigger>

Form integration

Pick your favorite.

Framework: not submitted
<Form onSubmit={onSubmit}>
  <Field>
    <FieldLabel>Framework</FieldLabel>
    <Select items={frameworks} name="framework" required>
      <SelectTrigger aria-label="Select framework">
        <SelectValue placeholder="Select a framework" />
      </SelectTrigger>
      <SelectPopup>
        {frameworks.map(({ label, value }) => (
          <SelectItem key={value} value={value}>{label}</SelectItem>
        ))}
      </SelectPopup>
    </Select>
    <FieldDescription>Pick your favorite.</FieldDescription>
    <FieldError>Please select a value.</FieldError>
  </Field>
  <Button loading={loading} type="submit">Submit</Button>
</Form>

With label

SelectLabel labels the control itself. Inside a form, prefer Field with FieldLabel.

Fruit
<Select defaultValue="apple" items={fruits}>
  <div className="grid w-56 justify-items-start">
    <SelectLabel>Fruit</SelectLabel>
    <SelectTrigger className="w-full">
      <SelectValue />
    </SelectTrigger>
  </div>
  <SelectPopup>
    {fruits.map(({ label, value }) => (
      <SelectItem key={value} value={value}>{label}</SelectItem>
    ))}
  </SelectPopup>
</Select>

Options with icons

A render function as SelectValue child formats the selected item; itemToStringValue keeps the form value a string.

const categories = [
  { icon: LayersIcon, label: "Components", value: "components" },
  { icon: ZapIcon, label: "Performance", value: "performance" },
];

<Select defaultValue={categories[0]} itemToStringValue={(item) => item.value}>
  <SelectTrigger aria-label="Select category">
    <SelectValue>
      {(item) => (
        <span className="flex items-center gap-2">
          <item.icon />
          {item.label}
        </span>
      )}
    </SelectValue>
  </SelectTrigger>
  <SelectPopup>
    {categories.map((item) => (
      <SelectItem key={item.value} value={item}>
        <span className="flex items-center gap-2">
          <item.icon />
          {item.label}
        </span>
      </SelectItem>
    ))}
  </SelectPopup>
</Select>

Object values

const starterTemplates = [
  { description: "npx create-next-app", label: "Next.js", value: "next" },
  { description: "npm create vite@latest", label: "Vite", value: "vite" },
];

<Select
  defaultValue={starterTemplates[0]}
  itemToStringValue={(item) => item.value}
>
  <SelectTrigger aria-label="Select starter template" className="py-1">
    <SelectValue>
      {(item) => (
        <span className="flex min-w-0 flex-col">
          <span className="truncate">{item.label}</span>
          <span className="truncate text-muted-foreground text-xs">
            {item.description}
          </span>
        </span>
      )}
    </SelectValue>
  </SelectTrigger>
  <SelectPopup>
    {starterTemplates.map((item) => (
      <SelectItem key={item.value} value={item}>
        <span className="flex min-w-0 flex-col">
          <span className="truncate">{item.label}</span>
          <span className="truncate text-muted-foreground text-xs">
            {item.description}
          </span>
        </span>
      </SelectItem>
    ))}
  </SelectPopup>
</Select>

SelectButton trigger

SelectButton renders the select trigger look as a plain button — here as the render trigger of a filterable Combobox.

import { SelectButton } from "@/components/ui/select";

<Combobox items={fruits}>
  <ComboboxTrigger render={<SelectButton />}>
    <ComboboxValue placeholder="Select a fruit" />
  </ComboboxTrigger>
  <ComboboxPopup aria-label="Select a fruit">
    <div className="p-2">
      <ComboboxInput
        placeholder="Search fruits..."
        showTrigger={false}
        startAddon={<SearchIcon />}
      />
    </div>
    <ComboboxEmpty>No items found.</ComboboxEmpty>
    <ComboboxList>
      {(item) => (
        <ComboboxItem key={item.value} value={item}>{item.label}</ComboboxItem>
      )}
    </ComboboxList>
  </ComboboxPopup>
</Combobox>

API Reference

The reference lists the source-owned exports and props that are easy to miss in visual examples. Inherited Base UI props remain available unless the wrapper narrows them.

  • Select supports single or multiple controlled and uncontrolled values through the Base UI root contract.
  • SelectTrigger size="xl" uses the 56px customer-facing field height and 16px field text. Pair it with a surrounding SiteplaneUIProvider radius="sm" for the global 8px main corner. Popup item density and radius stay independent from the trigger size.
  • Compose SelectTrigger, SelectValue, SelectPopup, SelectItem, SelectGroup, SelectGroupLabel, SelectLabel and SelectSeparator; SelectButton is the standalone trigger-style button.
  • SelectPopup accepts side, align, sideOffset, alignOffset, anchor, alignItemWithTrigger, portalProps and popup props.
  • SelectItem accepts disabled. Long lists automatically expose the built-in scroll-up and scroll-down controls when the popup overflows.

Motion

Component motion uses the shared Siteplane motion contract. See the global motion guide for provider setup, tokens and reduced-motion behavior.

  • Select uses the shared floating-panel transition. Item-aligned popups keep the selected item stable while using the shared fade and scale behavior.
  • Placement, collision handling and reduced motion remain inside the native primitive.

Accessibility

  • Give the trigger a visible label or accessible name and keep disabled or invalid state paired with clear text.
  • Preserve keyboard navigation, typeahead, focus return and the built-in long-list scroll controls.
  • A disabled option must remain understandable without being the only available choice.

Implementation Guidance

  • Use Combobox for large searchable sets and Autocomplete for suggestions while typing.
  • Configure placement on SelectPopup, not with local positioning wrappers.
  • Do not replace the built-in overflow behavior with a second nested scroll container.

On This Page