Github

Select

Displays a list of options for the user to pick from—triggered by a button.

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

const items = [
	{ label: "Select a fruit", value: null },
	{ label: "Apple", value: "apple" },
	{ label: "Banana", value: "banana" },
	{ label: "Blueberry", value: "blueberry" },
];

export function SelectDemo() {
	return (
		<Select items={items}>
			<SelectTrigger className="w-full max-w-48">
				<SelectValue />
			</SelectTrigger>
			<SelectContent>
				<SelectGroup>
					{items.map((item) => (
						<SelectItem key={item.label} value={item.value}>
							{item.label}
						</SelectItem>
					))}
				</SelectGroup>
			</SelectContent>
		</Select>
	);
}

Installation

pnpm dlx shadcn@latest add https://herocn.dev/r/select.json

Usage

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

Pass an items array to Select so SelectValue can show the label for the selected option. Render SelectItem children for each option (including groups and separators as needed).

const items = [
  { label: "Select a fruit", value: null },
  { label: "Apple", value: "apple" },
  { label: "Banana", value: "banana" },
]

<Select items={items}>
  <SelectTrigger className="w-full max-w-48">
    <SelectValue />
  </SelectTrigger>
  <SelectContent>
    <SelectGroup>
      {items.map((item) => (
        <SelectItem key={item.label} value={item.value}>
          {item.label}
        </SelectItem>
      ))}
    </SelectGroup>
  </SelectContent>
</Select>

Composition

Use the following composition to build a Select:

Select
├── SelectTrigger
│   └── SelectValue
└── SelectContent
    ├── SelectGroup
    │   ├── SelectLabel
    │   ├── SelectItem
    │   └── SelectItem
    ├── SelectSeparator
    └── SelectGroup
        ├── SelectLabel
        ├── SelectItem
        └── SelectItem

Examples

Groups

Use SelectGroup, SelectLabel, and SelectSeparator to organize items.

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

export function SelectGroups() {
	const fruits = [
		{ label: "Apple", value: "apple" },
		{ label: "Banana", value: "banana" },
		{ label: "Blueberry", value: "blueberry" },
	];
	const vegetables = [
		{ label: "Carrot", value: "carrot" },
		{ label: "Broccoli", value: "broccoli" },
		{ label: "Spinach", value: "spinach" },
	];
	const allItems = [
		{ label: "Select a fruit", value: null },
		...fruits,
		...vegetables,
	];
	return (
		<Select items={allItems}>
			<SelectTrigger className="w-full max-w-48">
				<SelectValue />
			</SelectTrigger>
			<SelectContent>
				<SelectGroup>
					<SelectLabel>Fruits</SelectLabel>
					{fruits.map((item) => (
						<SelectItem key={item.value} value={item.value}>
							{item.label}
						</SelectItem>
					))}
				</SelectGroup>
				<SelectSeparator />
				<SelectGroup>
					<SelectLabel>Vegetables</SelectLabel>
					{vegetables.map((item) => (
						<SelectItem key={item.value} value={item.value}>
							{item.label}
						</SelectItem>
					))}
				</SelectGroup>
			</SelectContent>
		</Select>
	);
}

Multiple

Set multiple on Select and control selection with an array value and onValueChange.

"use client";

import * as React from "react";
import {
	Select,
	SelectContent,
	SelectGroup,
	SelectItem,
	SelectTrigger,
	SelectValue,
} from "@/components/ui/select";

const fruits = [
	{ label: "Apple", value: "apple" },
	{ label: "Banana", value: "banana" },
	{ label: "Blueberry", value: "blueberry" },
	{ label: "Grapes", value: "grapes" },
	{ label: "Pineapple", value: "pineapple" },
];

export function SelectMultiple() {
	const [value, setValue] = React.useState<string[]>([]);

	return (
		<Select multiple items={fruits} value={value} onValueChange={setValue}>
			<SelectTrigger className="w-full max-w-sm">
				<SelectValue placeholder="Select fruits..." />
			</SelectTrigger>
			<SelectContent>
				<SelectGroup>
					{fruits.map((item) => (
						<SelectItem key={item.value} value={item.value}>
							{item.label}
						</SelectItem>
					))}
				</SelectGroup>
			</SelectContent>
		</Select>
	);
}

Scrollable

A select with many items scrolls inside the popup.

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

const northAmerica = [
	{ label: "Eastern Standard Time", value: "est" },
	{ label: "Central Standard Time", value: "cst" },
	{ label: "Mountain Standard Time", value: "mst" },
	{ label: "Pacific Standard Time", value: "pst" },
	{ label: "Alaska Standard Time", value: "akst" },
	{ label: "Hawaii Standard Time", value: "hst" },
];

const europeAfrica = [
	{ label: "Greenwich Mean Time", value: "gmt" },
	{ label: "Central European Time", value: "cet" },
	{ label: "Eastern European Time", value: "eet" },
	{ label: "Western European Summer Time", value: "west" },
	{ label: "Central Africa Time", value: "cat" },
	{ label: "East Africa Time", value: "eat" },
];

const asia = [
	{ label: "Moscow Time", value: "msk" },
	{ label: "India Standard Time", value: "ist" },
	{ label: "China Standard Time", value: "cst_china" },
	{ label: "Japan Standard Time", value: "jst" },
	{ label: "Korea Standard Time", value: "kst" },
	{ label: "Indonesia Central Standard Time", value: "ist_indonesia" },
];

const australiaPacific = [
	{ label: "Australian Western Standard Time", value: "awst" },
	{ label: "Australian Central Standard Time", value: "acst" },
	{ label: "Australian Eastern Standard Time", value: "aest" },
	{ label: "New Zealand Standard Time", value: "nzst" },
	{ label: "Fiji Time", value: "fjt" },
];

const southAmerica = [
	{ label: "Argentina Time", value: "art" },
	{ label: "Bolivia Time", value: "bot" },
	{ label: "Brasilia Time", value: "brt" },
	{ label: "Chile Standard Time", value: "clt" },
];

const items = [
	{ label: "Select a timezone", value: null },
	...northAmerica,
	...europeAfrica,
	...asia,
	...australiaPacific,
	...southAmerica,
];

export function SelectScrollable() {
	return (
		<Select items={items}>
			<SelectTrigger className="w-full max-w-64">
				<SelectValue />
			</SelectTrigger>
			<SelectContent>
				<SelectGroup>
					<SelectLabel>North America</SelectLabel>
					{northAmerica.map((item) => (
						<SelectItem key={item.value} value={item.value}>
							{item.label}
						</SelectItem>
					))}
				</SelectGroup>
				<SelectGroup>
					<SelectLabel>Europe & Africa</SelectLabel>
					{europeAfrica.map((item) => (
						<SelectItem key={item.value} value={item.value}>
							{item.label}
						</SelectItem>
					))}
				</SelectGroup>
				<SelectGroup>
					<SelectLabel>Asia</SelectLabel>
					{asia.map((item) => (
						<SelectItem key={item.value} value={item.value}>
							{item.label}
						</SelectItem>
					))}
				</SelectGroup>
				<SelectGroup>
					<SelectLabel>Australia & Pacific</SelectLabel>
					{australiaPacific.map((item) => (
						<SelectItem key={item.value} value={item.value}>
							{item.label}
						</SelectItem>
					))}
				</SelectGroup>
				<SelectGroup>
					<SelectLabel>South America</SelectLabel>
					{southAmerica.map((item) => (
						<SelectItem key={item.value} value={item.value}>
							{item.label}
						</SelectItem>
					))}
				</SelectGroup>
			</SelectContent>
		</Select>
	);
}

Disabled

Set disabled on the root Select to disable the whole control, or on individual SelectItem components to disable specific options.

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

const items: Array<{
	label: string;
	value: string | null;
	disabled?: boolean;
}> = [
	{ label: "Select a fruit", value: null },
	{ label: "Apple", value: "apple" },
	{ label: "Banana", value: "banana" },
	{ label: "Blueberry", value: "blueberry" },
	{ label: "Grapes", value: "grapes", disabled: true },
	{ label: "Pineapple", value: "pineapple" },
];

export function SelectDisabled() {
	return (
		<Select items={items} disabled>
			<SelectTrigger className="w-full max-w-48">
				<SelectValue />
			</SelectTrigger>
			<SelectContent>
				<SelectGroup>
					{items.map((item) => (
						<SelectItem
							key={item.label}
							value={item.value}
							disabled={item.disabled}
						>
							{item.label}
						</SelectItem>
					))}
				</SelectGroup>
			</SelectContent>
		</Select>
	);
}

Invalid

Add the data-invalid attribute to the Field component and the aria-invalid attribute to the SelectTrigger component to show an error state.

<Field data-invalid>
  <FieldLabel>Fruit</FieldLabel>
  <SelectTrigger aria-invalid>
    <SelectValue />
  </SelectTrigger>
</Field>
import { Field, FieldError, FieldLabel } from "@/components/ui/field";
import {
	Select,
	SelectContent,
	SelectGroup,
	SelectItem,
	SelectTrigger,
	SelectValue,
} from "@/components/ui/select";

const items = [
	{ label: "Select a fruit", value: null },
	{ label: "Apple", value: "apple" },
	{ label: "Banana", value: "banana" },
	{ label: "Blueberry", value: "blueberry" },
];

export function SelectInvalid() {
	return (
		<Field data-invalid className="w-full max-w-48">
			<FieldLabel>Fruit</FieldLabel>
			<Select items={items}>
				<SelectTrigger aria-invalid>
					<SelectValue />
				</SelectTrigger>
				<SelectContent>
					<SelectGroup>
						{items.map((item) => (
							<SelectItem key={item.label} value={item.value}>
								{item.label}
							</SelectItem>
						))}
					</SelectGroup>
				</SelectContent>
			</Select>
			<FieldError>Please select a fruit.</FieldError>
		</Field>
	);
}

Variants

The Select component supports two visual variants:

  • primary (default) - Standard styling with shadow, suitable for most use cases
  • secondary - Lower emphasis variant without shadow, suitable for use in Surface components
import {
	Select,
	SelectContent,
	SelectGroup,
	SelectItem,
	SelectTrigger,
	SelectValue,
} from "@/components/ui/select";

const items = [
	{ label: "Select a fruit", value: null },
	{ label: "Apple", value: "apple" },
	{ label: "Banana", value: "banana" },
	{ label: "Blueberry", value: "blueberry" },
];

export function SelectVariants() {
	return (
		<div className="flex w-full max-w-sm flex-col gap-4">
			<Select items={items} variant="default">
				<SelectTrigger className="w-full">
					<SelectValue />
				</SelectTrigger>
				<SelectContent>
					<SelectGroup>
						{items.map((item) => (
							<SelectItem key={item.label} value={item.value}>
								{item.label}
							</SelectItem>
						))}
					</SelectGroup>
				</SelectContent>
			</Select>
			<Select items={items} variant="secondary">
				<SelectTrigger className="w-full">
					<SelectValue />
				</SelectTrigger>
				<SelectContent>
					<SelectGroup>
						{items.map((item) => (
							<SelectItem key={item.label} value={item.value}>
								{item.label}
							</SelectItem>
						))}
					</SelectGroup>
				</SelectContent>
			</Select>
		</div>
	);
}

In Surface

Place the select inside a Surface panel. Use variant="secondary" on Select so the trigger sits cleanly on the surface background.

Appearance

Choose how the interface looks on this device.

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

const items = [
	{ label: "Select a theme", value: null },
	{ label: "Light", value: "light" },
	{ label: "Dark", value: "dark" },
	{ label: "System", value: "system" },
];

export function SelectSurface() {
	return (
		<Surface className="w-full max-w-sm rounded-3xl border p-4">
			<h4 className="font-medium text-sm">Appearance</h4>
			<p className="mt-1 text-muted-foreground text-sm">
				Choose how the interface looks on this device.
			</p>
			<div className="mt-4">
				<Select items={items} variant="secondary">
					<SelectTrigger className="w-full">
						<SelectValue />
					</SelectTrigger>
					<SelectContent>
						<SelectGroup>
							{items.map((item) => (
								<SelectItem key={item.label} value={item.value}>
									{item.label}
								</SelectItem>
							))}
						</SelectGroup>
					</SelectContent>
				</Select>
			</div>
		</Surface>
	);
}

RTL

To enable RTL support in shadcn/ui, see the RTL configuration guide.

"use client";

import * as React from "react";

import {
	type Translations,
	useTranslation,
} from "@/components/language-selector";
import {
	Select,
	SelectContent,
	SelectGroup,
	SelectItem,
	SelectLabel,
	SelectSeparator,
	SelectTrigger,
	SelectValue,
} from "@/components/ui/select";

const translations: Translations = {
	en: {
		dir: "ltr",
		values: {
			selectFruit: "Select a fruit",
			fruits: "Fruits",
			apple: "Apple",
			banana: "Banana",
			blueberry: "Blueberry",
			grapes: "Grapes",
			pineapple: "Pineapple",
			vegetables: "Vegetables",
			carrot: "Carrot",
			broccoli: "Broccoli",
			spinach: "Spinach",
		},
	},
	ar: {
		dir: "rtl",
		values: {
			selectFruit: "اختر فاكهة",
			fruits: "الفواكه",
			apple: "تفاح",
			banana: "موز",
			blueberry: "توت أزرق",
			grapes: "عنب",
			pineapple: "أناناس",
			vegetables: "الخضروات",
			carrot: "جزر",
			broccoli: "بروكلي",
			spinach: "سبانخ",
		},
	},
	he: {
		dir: "rtl",
		values: {
			selectFruit: "בחר פרי",
			fruits: "פירות",
			apple: "תפוח",
			banana: "בננה",
			blueberry: "אוכמניה",
			grapes: "ענבים",
			pineapple: "אננס",
			vegetables: "ירקות",
			carrot: "גזר",
			broccoli: "ברוקולי",
			spinach: "תרד",
		},
	},
};

export function SelectRtl() {
	const { dir, t, language } = useTranslation(translations, "ar");
	const [selectedFruit, setSelectedFruit] = React.useState<string | null>(null);

	const fruits = [
		{ label: t.apple, value: "apple" },
		{ label: t.banana, value: "banana" },
		{ label: t.blueberry, value: "blueberry" },
		{ label: t.grapes, value: "grapes" },
		{ label: t.pineapple, value: "pineapple" },
	];

	const vegetables = [
		{ label: t.carrot, value: "carrot" },
		{ label: t.broccoli, value: "broccoli" },
		{ label: t.spinach, value: "spinach" },
	];

	const allItems = [
		{ label: t.selectFruit, value: null },
		...fruits,
		...vegetables,
	];

	return (
		<Select
			items={allItems}
			value={selectedFruit}
			data-lang={language}
			onValueChange={setSelectedFruit}
		>
			<SelectTrigger
				data-lang={dir === "rtl" ? language : undefined}
				className="w-auto min-w-32"
				dir={dir}
			>
				<SelectValue />
			</SelectTrigger>
			<SelectContent dir={dir} data-lang={dir === "rtl" ? language : undefined}>
				<SelectGroup>
					<SelectLabel>{t.fruits}</SelectLabel>
					{fruits.map((item) => (
						<SelectItem key={item.value} value={item.value}>
							{item.label}
						</SelectItem>
					))}
				</SelectGroup>
				<SelectSeparator />
				<SelectGroup>
					<SelectLabel>{t.vegetables}</SelectLabel>
					{vegetables.map((item) => (
						<SelectItem key={item.value} value={item.value}>
							{item.label}
						</SelectItem>
					))}
				</SelectGroup>
			</SelectContent>
		</Select>
	);
}

API Reference

The select is built on Base UI Select. Select forwards props to the root; SelectTrigger adds styling and a size option.

Select

PropTypeDefault

See the Base UI Select API reference for all other props (items, value, onValueChange, positioning, etc.).