Select
The trigger is the same inset well as Input. The list drops from it in a square box lifted by a double outline; the highlighted row fills with the accent.
Install
# once per project: where @zeroui items come frompnpm dlx shadcn@latest registry add @zeroui=https://zeroui.catforest.dev/r/{name}.jsonpnpm dlx shadcn@latest add @zeroui/selectExamples
Basic
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "@/components/zero/select";const districts = { harbor: "Harbor", market: "Old market", rail: "Rail yards", canal: "Canal side",};export default function SelectBasic() { return ( <Select items={districts}> <SelectTrigger className="max-w-64"> <SelectValue placeholder="Pick a district" /> </SelectTrigger> <SelectContent> {Object.entries(districts).map(([value, label]) => ( <SelectItem key={value} value={value}> {label} </SelectItem> ))} </SelectContent> </Select> );}Groups, a separator, a disabled row
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue,} from "@/components/zero/select";const districts = { harbor: "Harbor", canal: "Canal side", market: "Old market", rail: "Rail yards", tower: "Tower row",};export default function SelectGroups() { return ( <Select items={districts} defaultValue="market"> <SelectTrigger className="max-w-64"> <SelectValue /> </SelectTrigger> <SelectContent> <SelectGroup> <SelectLabel>Waterfront</SelectLabel> <SelectItem value="harbor">Harbor</SelectItem> <SelectItem value="canal">Canal side</SelectItem> </SelectGroup> <SelectSeparator /> <SelectGroup> <SelectLabel>Inland</SelectLabel> <SelectItem value="market">Old market</SelectItem> <SelectItem value="rail">Rail yards</SelectItem> <SelectItem value="tower" disabled> Tower row </SelectItem> </SelectGroup> </SelectContent> </Select> );}In a Field
import { Field, FieldError, FieldLabel } from "@/components/zero/field";import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "@/components/zero/select";const shifts = { dawn: "Dawn", day: "Day", night: "Night" };export default function SelectField() { return ( <Field invalid className="w-full max-w-64"> <FieldLabel>Shift</FieldLabel> <Select items={shifts} defaultValue="night"> <SelectTrigger> <SelectValue /> </SelectTrigger> <SelectContent> {Object.entries(shifts).map(([value, label]) => ( <SelectItem key={value} value={value}> {label} </SelectItem> ))} </SelectContent> </Select> <FieldError match>No crew is free at night this week.</FieldError> </Field> );}Behaviour
The popup grows out of the trigger in 240 ms and folds away in 140 ms. It always drops below (or above, if there is no room) rather than laying the chosen row over the trigger, so a long list scrolls inside it.
Pass items to Select (value to label) so the trigger can show the chosen label before the list has ever opened.
Props
Select
| Prop | Type | Default | Description |
|---|---|---|---|
| items | Record<string, ReactNode> | — | Labels by value, for the trigger. |
Also takes every prop of Base UI's Select.Root.
SelectTrigger
| Prop | Type | Default | Description |
|---|---|---|---|
| size | "sm" | "md" | "lg" | "md" | 32, 40 or 52 px tall. |
| cut | "end" | "start" | "both" | "none" | "end" | Which edges of the well are cut on the slant. |
Also takes every prop of Base UI's Select.Trigger.
SelectValue
| Prop | Type | Default | Description |
|---|---|---|---|
| placeholder | ReactNode | — | Shown, in italic grey, until something is chosen. |
Also takes every prop of Base UI's Select.Value.
SelectContent
| Prop | Type | Default | Description |
|---|---|---|---|
| side | "bottom" | "top" | "left" | "right" | "bottom" | Where the popup opens from the trigger. |
| align | "start" | "center" | "end" | "start" | How it lines up with the trigger. |
Also takes every prop of Base UI's Select.Popup.
SelectItem
Also takes every prop of Base UI's Select.Item.
SelectGroup, SelectLabel, SelectSeparator
Also takes every prop of Base UI's Select.Group.