select
Displays a list of options for the user to pick from - triggered by a button.
Philosophy
Native selects are accessible but difficult to style consistently. This select builds on @gentleduck/primitives/select which provides full keyboard navigation, ARIA semantics, scroll management, and typeahead search. The wrapper adds design-system styling while the primitive handles all interaction logic.
How It's Built
Installation
npx @gentleduck/cli add select
npx @gentleduck/cli add select
Usage
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
<Select>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder="Theme" />
</SelectTrigger>
<SelectContent>
<SelectItem value="light">Light</SelectItem>
<SelectItem value="dark">Dark</SelectItem>
<SelectItem value="system">System</SelectItem>
</SelectContent>
</Select>
Examples
Scrollable
Form
Active Trigger on Open
Style the trigger to appear active while the select dropdown is open using data-[state=open]:
Component Composition
RTL Support
Set the direction once with DirectionProvider (or <html dir="rtl">) and the select follows. The portaled listbox gets the resolved dir, so text, the check indicator, and alignment mirror without any extra props.
Motion
Use MotionSelect and MotionSelectContent for smooth enter/exit animations powered by motion. The dropdown enters and exits with a scaleIn preset (fade, scale, and blur) and a bouncy spring transition. MotionSelectTrigger adds tap feedback (scale 0.97) to the trigger button.
motion package. Use MotionSelect instead of Select and MotionSelectContent instead of SelectContent. Optionally use MotionSelectTrigger instead of SelectTrigger for tap feedback. All other sub-components stay the same.API Reference
Select
The root component that manages open/closed state, value state, and provides context.
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | - | Controlled selected value |
defaultValue | string | - | Initial value (uncontrolled) |
onValueChange | (value: string) => void | - | Callback fired when the selected value changes |
open | boolean | - | Controlled open state |
defaultOpen | boolean | false | Initial open state (uncontrolled) |
onOpenChange | (open: boolean) => void | - | Callback fired when dropdown open state changes |
dir | 'ltr' | 'rtl' | - | Text direction. Resolved by primitives useDirection (dir prop -> DirectionProvider -> <html dir>). |
name | string | - | Name attribute for native form submission |
disabled | boolean | - | Disables the entire select |
required | boolean | - | Marks the select as required for form validation |
form | string | - | Associates the select with a form element by ID |
autoComplete | string | - | Hint for browser autofill |
SelectTrigger
Button that toggles the dropdown. Renders a styled <button> with a chevron icon.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional CSS class names |
disabled | boolean | - | Disables the trigger |
Sets role="combobox", aria-expanded, aria-controls, aria-autocomplete="none", and data-state automatically.
SelectValue
Renders the selected value text, or a placeholder when nothing is selected.
| Prop | Type | Default | Description |
|---|---|---|---|
placeholder | React.ReactNode | '' | Text shown when no value is selected |
SelectContent
The dropdown content area. Handles positioning, focus management, keyboard navigation, and dismiss behavior.
| Prop | Type | Default | Description |
|---|---|---|---|
position | 'item-aligned' | 'popper' | 'popper' | Positioning strategy. item-aligned aligns selected item with trigger. popper uses floating positioning. |
side | 'top' | 'right' | 'bottom' | 'left' | 'bottom' | Preferred side (popper position only) |
sideOffset | number | - | Offset from trigger on the main axis |
align | 'start' | 'center' | 'end' | 'start' | Alignment on the cross axis |
alignOffset | number | - | Offset on the cross axis |
avoidCollisions | boolean | true | Whether to flip to avoid viewport overflow |
collisionPadding | number | 10 | Padding from viewport edges |
className | string | - | Additional CSS class names |
Exposes data-state="open" / data-state="closed" and data-side for CSS animation.
SelectGroup
Groups related items together. Renders a <div> with role="group".
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional CSS class names |
SelectLabel
Label for a SelectGroup. Renders a <div> linked to its group via aria-labelledby.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional CSS class names |
SelectItem
An individual selectable item. Renders a <div> with role="option".
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | (required) | The value representing this item. Must not be empty string. |
disabled | boolean | false | Whether the item is disabled |
textValue | string | - | Optional text override for typeahead search |
className | string | - | Additional CSS class names |
Exposes data-state="checked" / data-state="unchecked", data-highlighted, and data-disabled.
SelectSeparator
Visual separator between items. Renders a styled <div>.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional CSS class names |
SelectScrollUpButton / SelectScrollDownButton
Scroll navigation buttons that appear when content overflows the viewport.
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | - | Additional CSS class names |
MotionSelect
Same props as Select. Wraps the root with motion state management for enter/exit animations. Required when using MotionSelectContent.
MotionSelectContent
Same props as SelectContent. Adds a scaleIn enter/exit animation (fade, scale, and blur) with a springBouncy transition, mounted and unmounted via useMotionRoot/useMotionMount for exit-animation support. Must be used with MotionSelect root. Requires the motion package.
MotionSelectTrigger
Same props as SelectTrigger. Wraps the trigger with whileTap press feedback (scale 0.97). Requires the motion package.
| Prop | Type | Default | Description |
|---|---|---|---|
...props | ISelectTriggerProps | - | All props from SelectTrigger are supported |