Select
Displays a list of options for the user to pick from.
Playground
Installation
Install in any React + Tailwind project:
$npx shadcn@latest add https://twigs.globirdenergy.com.au/r/select.json
Source
The exact file the registry serves to consumers.
'use client';
import * as React from 'react';
import { Select as BaseSelect } from '@base-ui/react/select';
import { Check, ChevronDown, ChevronUp } from 'lucide-react';
import { cn } from '@/lib/utils';
const Select = BaseSelect.Root;
const SelectGroup = BaseSelect.Group;
function SelectValue({
placeholder,
className,
...props
}: React.ComponentProps<typeof BaseSelect.Value> & { placeholder?: React.ReactNode }) {
return (
<BaseSelect.Value
data-slot="select-value"
className={className}
{...props}
>
{(value: unknown) => (value == null || value === '' ? placeholder : (value as React.ReactNode))}
</BaseSelect.Value>
);
}
function SelectTrigger({
className,
children,
...props
}: React.ComponentProps<typeof BaseSelect.Trigger>) {
return (
// Spec source: Figma "APP Re-design Phase 1" → page "Select ✅"
<BaseSelect.Trigger
data-slot="select-trigger"
className={cn(
"data-[panel-open]:border-ring focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 aria-invalid:border-destructive flex h-10 w-fit items-center justify-between gap-2 rounded-xl bg-secondary px-4 py-2 text-sm font-medium whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 *: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-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
className,
)}
{...props}
>
{children}
<BaseSelect.Icon className="size-4 opacity-50">
<ChevronDown className="size-4" />
</BaseSelect.Icon>
</BaseSelect.Trigger>
);
}
function SelectContent({
className,
children,
...props
}: React.ComponentProps<typeof BaseSelect.Popup>) {
return (
<BaseSelect.Portal>
<BaseSelect.Positioner sideOffset={4}>
<BaseSelect.Popup
data-slot="select-content"
className={cn(
'bg-popover text-popover-foreground z-50 max-h-[--available-height] min-w-[var(--anchor-width)] overflow-hidden rounded-xl border shadow-lg',
'data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 transition-opacity',
className,
)}
{...props}
>
<BaseSelect.ScrollUpArrow className="flex h-6 cursor-default items-center justify-center">
<ChevronUp className="size-4" />
</BaseSelect.ScrollUpArrow>
<BaseSelect.List className="p-1">{children}</BaseSelect.List>
<BaseSelect.ScrollDownArrow className="flex h-6 cursor-default items-center justify-center">
<ChevronDown className="size-4" />
</BaseSelect.ScrollDownArrow>
</BaseSelect.Popup>
</BaseSelect.Positioner>
</BaseSelect.Portal>
);
}
function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof BaseSelect.Item>) {
return (
<BaseSelect.Item
data-slot="select-item"
className={cn(
"data-[highlighted]:bg-secondary data-[highlighted]:text-foreground relative flex w-full cursor-default items-center gap-2 rounded-md py-2 pr-8 pl-3 text-sm outline-hidden select-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className,
)}
{...props}
>
<span className="absolute right-2 flex size-3.5 items-center justify-center">
<BaseSelect.ItemIndicator>
<Check className="size-4" />
</BaseSelect.ItemIndicator>
</span>
<BaseSelect.ItemText>{children}</BaseSelect.ItemText>
</BaseSelect.Item>
);
}
export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectItem };