Select
A dropdown select control with multiple size variants.
Installation
$
Usage
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select"Examples
Default
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
| sizeSelectTrigger | "xs" | "sm" | "default" | "default" | Trigger height and text size. |
| positionSelectContent | "item-aligned" | "popper" | "popper" | Dropdown positioning strategy: item-aligned overlays the trigger like a native select, popper anchors against it like a popover. |
| alignSelectContent | "start" | "center" | "end" | "center" | Alignment against the trigger along the chosen side; only applies when position is popper. |
| value*SelectItem | string | - | The value submitted when the item is selected. |
| defaultValueSelect | string | - | The initially selected value for uncontrolled usage. |
| onValueChangeSelect | (value: string) => void | - | Called with the new value when the selection changes. Pair with value for controlled usage. |
| disabledSelect | boolean | false | Disables the entire select. |
| disabledSelectItem | boolean | false | Disables a single option. |
"use client"
import * as React from "react"
import { Select as SelectPrimitive } from "@base-ui/react/select"
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
import { cn } from "@/registry/lib/utils"
/**
* Tracks whether a label has a group to name. Base UI's `Select.GroupLabel`
* throws when it has no `Select.Group` ancestor, and because the popup only
* mounts on open, neither SSR nor a typecheck catches a bare `<SelectLabel>`:
* it crashes in the user's hands. Inside a group the label stays the real
* `GroupLabel` that carries the `aria-labelledby` wiring; standalone it degrades
* to a plain div. Mirrors the same guard in dropdown-menu.
*/
const SelectGroupContext = React.createContext(false)
function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />
}
function SelectGroup({
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return (
<SelectGroupContext.Provider value={true}>
<SelectPrimitive.Group data-slot="select-group" {...props} />
</SelectGroupContext.Provider>
)
}
function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />
}
function SelectTrigger({
className,
size = "default" as "xs" | "sm" | "default",
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: "xs" | "sm" | "default"
}) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
"border-border data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-accent focus-visible:ring-ring-accent aria-invalid:ring-ring-destructive aria-invalid:border-destructive flex w-fit items-center justify-between gap-2 data-[size=default]:rounded-action data-[size=sm]:rounded-action-sm data-[size=xs]:rounded-action-xs border bg-input-bg backdrop-blur-sm px-3 text-base md:text-sm font-medium normal-case tracking-normal whitespace-nowrap shadow-xs transition-[color,box-shadow,border-color] duration-150 outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-70 data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=xs]:h-6 data-[size=xs]:px-2 data-[size=xs]:text-xs data-[size=xs]:gap-1 pointer-coarse:data-[size=xs]:min-h-8 pointer-coarse:data-[size=sm]:min-h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon
render={<ChevronDownIcon className="size-3 opacity-50" />}
/>
</SelectPrimitive.Trigger>
)
}
function SelectContent({
className,
children,
position = "popper",
align = "center",
side,
sideOffset = 4,
alignOffset,
...props
}: React.ComponentProps<typeof SelectPrimitive.Popup> &
Pick<
React.ComponentProps<typeof SelectPrimitive.Positioner>,
"align" | "side" | "sideOffset" | "alignOffset"
> & {
position?: "item-aligned" | "popper"
}) {
return (
<SelectPrimitive.Portal>
<SelectPrimitive.Positioner
side={side}
align={align}
sideOffset={sideOffset}
alignOffset={alignOffset}
alignItemWithTrigger={position === "item-aligned"}
className="z-50"
>
<SelectPrimitive.Popup
data-slot="select-content"
className={cn(
"bg-popover text-popover-foreground transition-[opacity,scale,translate] motion-reduce:transition-[opacity] ease-out duration-200 data-[ending-style]:duration-150 data-[starting-style]:opacity-0 data-[ending-style]:opacity-0 data-[starting-style]:scale-95 data-[ending-style]:scale-95 data-[side=bottom]:data-[starting-style]:-translate-y-2 data-[side=bottom]:data-[ending-style]:-translate-y-2 data-[side=top]:data-[starting-style]:translate-y-2 data-[side=top]:data-[ending-style]:translate-y-2 data-[side=right]:data-[starting-style]:-translate-x-2 data-[side=right]:data-[ending-style]:-translate-x-2 data-[side=left]:data-[starting-style]:translate-x-2 data-[side=left]:data-[ending-style]:translate-x-2 relative z-50 max-h-(--available-height) min-w-32 w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg shadow-ring-md",
className
)}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.List className="p-1 scroll-my-1">
{children}
</SelectPrimitive.List>
<SelectScrollDownButton />
</SelectPrimitive.Popup>
</SelectPrimitive.Positioner>
</SelectPrimitive.Portal>
)
}
function SelectLabel({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.GroupLabel>) {
// See SelectGroupContext: GroupLabel throws outside a group.
const inGroup = React.useContext(SelectGroupContext)
const Component = (
inGroup ? SelectPrimitive.GroupLabel : "div"
) as React.ElementType
return (
<Component
data-slot="select-label"
className={cn("text-muted-foreground px-2 py-1.5 text-xs font-medium", className)}
{...props}
/>
)
}
function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"data-[highlighted]:bg-accent-100 dark:data-[highlighted]:bg-accent-900 data-[highlighted]:text-accent [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-md py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className
)}
{...props}
>
<span
data-slot="select-item-indicator"
className="absolute right-2 flex size-3.5 items-center justify-center"
>
<SelectPrimitive.ItemIndicator>
<CheckIcon className="size-3" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
)
}
function SelectSeparator({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
{...props}
/>
)
}
function SelectScrollUpButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {
return (
<SelectPrimitive.ScrollUpArrow
data-slot="select-scroll-up-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUpIcon className="size-3" />
</SelectPrimitive.ScrollUpArrow>
)
}
function SelectScrollDownButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {
return (
<SelectPrimitive.ScrollDownArrow
data-slot="select-scroll-down-button"
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDownIcon className="size-3" />
</SelectPrimitive.ScrollDownArrow>
)
}
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
}