- Accordion
- Alert
- Alert Dialog
- Autocomplete
- Avatar
- Badge
- Breadcrumb
- Button
- Card
- Checkbox
- Checkbox Group
- Collapsible
- Combobox
- Dialog
- EmptyNew
- Field
- Fieldset
- Form
- Frame
- Group
- Input
- Input GroupNew
- KbdNew
- Label
- Menu
- Meter
- Number Field
- Pagination
- Popover
- Preview Card
- Progress
- Radio Group
- Scroll Area
- Select
- Separator
- Sheet
- SkeletonNew
- Slider
- SpinnerNew
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Toggle Group
- Toolbar
- Tooltip
Select
A common form component for choosing a predefined value in a dropdown menu.
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const items = [
{ label: "Select framework", value: null },
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
];
export default function Particle() {
return (
<Select defaultValue="next" items={items}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectPopup>
{items.map(({ label, value }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectPopup>
</Select>
);
}
Installation
pnpm dlx shadcn@latest add @coss/select
Usage
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"const items = [
{ label: "Select framework", value: null },
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
]
<Select items={items}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectPopup>
{items.map((item) => (
<SelectItem key={item.value} value={item}>
{item.label}
</SelectItem>
))}
</SelectPopup>
</Select>Examples
For accessible labelling and validation, prefer using the Field component to wrap selects. See the related example: Select field.
Small Size
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const items = [
{ label: "Select framework", value: null },
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
];
export default function Particle() {
return (
<Select aria-label="Select framework" items={items}>
<SelectTrigger size="sm">
<SelectValue />
</SelectTrigger>
<SelectPopup>
{items.map(({ label, value }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectPopup>
</Select>
);
}
Large Size
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const items = [
{ label: "Select framework", value: null },
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
];
export default function Particle() {
return (
<Select aria-label="Select framework" items={items}>
<SelectTrigger size="lg">
<SelectValue />
</SelectTrigger>
<SelectPopup>
{items.map(({ label, value }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectPopup>
</Select>
);
}
Disabled
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const items = [
{ label: "Select framework", value: null },
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
];
export default function Particle() {
return (
<Select aria-label="Select framework" items={items}>
<SelectTrigger disabled>
<SelectValue />
</SelectTrigger>
<SelectPopup>
{items.map(({ label, value }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectPopup>
</Select>
);
}
Without Item Alignment
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const items = [
{ label: "Select framework", value: null },
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
];
export default function Particle() {
return (
<Select aria-label="Select framework" items={items}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectPopup alignItemWithTrigger={false}>
{items.map(({ label, value }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectPopup>
</Select>
);
}
With Groups
import {
Select,
SelectGroup,
SelectGroupLabel,
SelectItem,
SelectPopup,
SelectSeparator,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const placeholder = [{ label: "Select framework", value: null }];
const frontend = [
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
];
const backend = [
{ label: "Express", value: "express" },
{ label: "NestJS", value: "nestjs" },
{ label: "Fastify", value: "fastify" },
{ label: "Django", value: "django" },
{ label: "Flask", value: "flask" },
{ label: "Rails", value: "rails" },
];
export default function Particle() {
return (
<Select
aria-label="Select framework"
items={[...placeholder, ...frontend, ...backend]}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectPopup>
<SelectGroup>
<SelectGroupLabel>Frontend</SelectGroupLabel>
{frontend.map(({ label, value }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectGroup>
<SelectSeparator />
<SelectGroup>
<SelectGroupLabel>Backend</SelectGroupLabel>
{backend.map(({ label, value }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectGroup>
</SelectPopup>
</Select>
);
}
Multiple Selection
"use client";
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const languages = {
cpp: "C++",
csharp: "C#",
go: "Go",
java: "Java",
javascript: "JavaScript",
php: "PHP",
python: "Python",
rust: "Rust",
swift: "Swift",
typescript: "TypeScript",
};
type Language = keyof typeof languages;
const values = Object.keys(languages) as Language[];
function renderValue(value: Language[]) {
if (value.length === 0) {
return "Select languages…";
}
const firstLanguage = value[0] ? languages[value[0]] : "";
const additionalLanguages =
value.length > 1 ? ` (+${value.length - 1} more)` : "";
return firstLanguage + additionalLanguages;
}
export default function Particle() {
return (
<Select
aria-label="Select languages"
defaultValue={["javascript", "typescript"]}
multiple
>
<SelectTrigger>
<SelectValue>{renderValue}</SelectValue>
</SelectTrigger>
<SelectPopup alignItemWithTrigger={false}>
{values.map((value) => (
<SelectItem key={value} value={value}>
{languages[value]}
</SelectItem>
))}
</SelectPopup>
</Select>
);
}
With Icon
import { ClockIcon } from "lucide-react";
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const items = [
{ label: "Select framework", value: null },
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
];
export default function Particle() {
return (
<Select
aria-label="Select framework with icon"
defaultValue="next"
items={items}
>
<SelectTrigger>
<ClockIcon />
<SelectValue />
</SelectTrigger>
<SelectPopup alignItemWithTrigger={false}>
{items.map(({ label, value }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectPopup>
</Select>
);
}
Options with Icon
"use client";
import { Code2Icon, GlobeIcon, LayersIcon, ZapIcon } from "lucide-react";
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const items = [
{ icon: LayersIcon, label: "Components", value: "components" },
{ icon: ZapIcon, label: "Performance", value: "performance" },
{ icon: GlobeIcon, label: "Network", value: "network" },
{ icon: Code2Icon, label: "Development", value: "development" },
];
export default function Particle() {
return (
<Select
aria-label="Select category"
defaultValue={items[0]}
itemToStringValue={(item) => item.value}
>
<SelectTrigger>
<SelectValue>
{(item) => (
<span className="flex items-center gap-2">
<item.icon className="size-4 opacity-72" />
<span className="truncate">{item.label}</span>
</span>
)}
</SelectValue>
</SelectTrigger>
<SelectPopup>
{items.map((item) => (
<SelectItem key={item.value} value={item}>
<span className="flex items-center gap-2">
<item.icon className="size-4 opacity-72" />
<span className="truncate">{item.label}</span>
</span>
</SelectItem>
))}
</SelectPopup>
</Select>
);
}
With Object Values
"use client";
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const items = [
{ description: "npx create-next-app", label: "Next.js", value: "next" },
{ description: "npm create vite@latest", label: "Vite", value: "vite" },
{ description: "npm create astro@latest", label: "Astro", value: "astro" },
{ description: "npx create-remix", label: "Remix", value: "remix" },
];
export default function Particle() {
return (
<Select
aria-label="Select framework with command"
defaultValue={items[0]}
itemToStringValue={(item) => item.value}
>
<SelectTrigger>
<SelectValue>
{(item) => (
<span className="flex flex-col">
<span className="truncate">{item.label}</span>
<span className="truncate text-muted-foreground text-xs">
{item.description}
</span>
</span>
)}
</SelectValue>
</SelectTrigger>
<SelectPopup>
{items.map((item) => (
<SelectItem key={item.value} value={item}>
<span className="flex flex-col">
<span className="truncate">{item.label}</span>
<span className="truncate text-muted-foreground text-xs">
{item.description}
</span>
</span>
</SelectItem>
))}
</SelectPopup>
</Select>
);
}
Form Integration
"use client";
import * as React from "react";
import { Button } from "@/components/ui/button";
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from "@/components/ui/field";
import { Form } from "@/components/ui/form";
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
const items = [
{ label: "Select a framework", value: null },
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
];
export default function Particle() {
const [loading, setLoading] = React.useState(false);
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
setLoading(true);
await new Promise((r) => setTimeout(r, 800));
setLoading(false);
alert(`Framework: ${formData.get("framework") || ""}`);
};
return (
<Form className="max-w-64" onSubmit={onSubmit}>
<Field>
<FieldLabel>Framework</FieldLabel>
<Select
aria-label="Select framework"
disabled={loading}
items={items}
name="framework"
required
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectPopup>
{items.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 disabled={loading} type="submit">
Submit
</Button>
</Form>
);
}
Comparing with Radix / shadcn
If you’re already familiar with Radix UI and shadcn/ui, this guide highlights the small differences and similarities so you can get started with coss ui quickly.
Important: Base UI changes how options are provided. Instead of deriving options from children only (Radix pattern), you should pass an items prop (array or record) so values and labels are known before hydration. This avoids SSR pitfalls and improves mount performance. Alternatively, provide a function child to SelectValue to format the label. See the Base UI Select docs.
Prop Mapping
| Component | Radix UI Prop | Base UI Prop |
|---|---|---|
Select | items | items |
SelectValue | placeholder | removed |
SelectPopup | alignItemWithTrigger | no equivalent |
Quick Checklist
- Set
itemsprop onSelect - Remove
placeholderfromSelect - Prefer
SelectPopupinstead ofSelectContent - If you used
asChildon parts, switch to therenderprop
Additional Notes
Size Comparison
coss ui select sizes are more compact compared to shadcn/ui, making them better suited for dense applications:
| Size | Height (shadcn/ui) | Height (coss ui) |
|---|---|---|
sm | 32px | 28px |
default | 36px | 32px |
lg | - | 36px |
So, for example, if you were using the default size in shadcn/ui and you want to preserve the original height, you should use the lg size in coss ui.
New Prop
Base UI introduces the alignItemWithTrigger prop to control whether the SelectContent overlaps the SelectTrigger so the selected item's text is aligned with the trigger's value text.
Comparison Example
<Select>
<SelectTrigger>
<SelectValue placeholder="Select a framework" />
</SelectTrigger>
<SelectContent>
<SelectItem value="next">Next.js</SelectItem>
<SelectItem value="vite">Vite</SelectItem>
<SelectItem value="astro">Astro</SelectItem>
</SelectContent>
</Select><Select
items={[
{ label: "Select a framework", value: null },
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
]}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectPopup alignItemWithTrigger={false}>
{items.map((item) => (
<SelectItem key={item.value} value={item}>
{item.label}
</SelectItem>
))}
</SelectPopup>
</Select>