Select
An accessible dropdown select for choosing one option from a list. Built on Radix UI Select with full keyboard navigation, typeahead, and RTL support.
With label
Grouped options (with separator)
With disabled option
Disabled
Installation
pnpm dlx @hex-core/cli add selectGrouped options
Cluster long option lists (timezones, countries) under SelectLabel headings so users can scan by region.
<Select defaultValue="America/Los_Angeles">
<SelectTrigger className="w-[260px]">
<SelectValue placeholder="Select a timezone" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Americas</SelectLabel>
<SelectItem value="America/Los_Angeles">Los Angeles (PT)</SelectItem>
<SelectItem value="America/Denver">Denver (MT)</SelectItem>
<SelectItem value="America/New_York">New York (ET)</SelectItem>
</SelectGroup>
<SelectGroup>
<SelectLabel>Europe</SelectLabel>
<SelectItem value="Europe/London">London (GMT)</SelectItem>
<SelectItem value="Europe/Berlin">Berlin (CET)</SelectItem>
<SelectItem value="Europe/Athens">Athens (EET)</SelectItem>
</SelectGroup>
<SelectGroup>
<SelectLabel>Asia</SelectLabel>
<SelectItem value="Asia/Tokyo">Tokyo (JST)</SelectItem>
<SelectItem value="Asia/Singapore">Singapore (SGT)</SelectItem>
<SelectItem value="Asia/Kolkata">Kolkata (IST)</SelectItem>
</SelectGroup>
</SelectContent>
</Select>Controlled with onValueChange
Two-way bind the select value to parent state — useful for filter dropdowns that drive a query.
import { useState } from "react";
type Status = "all" | "open" | "in-progress" | "closed";
export function StatusFilter() {
const [status, setStatus] = useState<Status>("all");
return (
<div className="grid gap-2">
<Label htmlFor="status">Status</Label>
<Select value={status} onValueChange={(value) => setStatus(value as Status)}>
<SelectTrigger id="status" className="w-[200px]">
<SelectValue placeholder="Filter by status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All issues</SelectItem>
<SelectItem value="open">Open</SelectItem>
<SelectItem value="in-progress">In progress</SelectItem>
<SelectItem value="closed">Closed</SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">Showing {status === "all" ? "all issues" : `${status} issues`}.</p>
</div>
);
}API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | — | [Select root prop] Controlled selected value |
defaultValue | string | — | [Select root prop] Default selected value for uncontrolled usage |
onValueChange | function | — | [Select root prop] Callback on value change: (value: string) => void |
disabled | boolean | false | [Select root prop] Disable the entire select |
required | boolean | false | [Select root prop] Mark as required for form validation |
name | string | — | [Select root prop] Form field name (for native form submission) |
AI Guidance
When to use
Use for choosing one option from a known, finite list (<= ~20 items): timezones, categories, roles, country codes. Pair with Label.
When not to use
Don't use for large searchable lists (use Combobox). Don't use for boolean choices (use Switch/Checkbox). Don't use for action menus (use DropdownMenu). Don't use for multi-select (needs a different component).
Common mistakes
- Missing Label pairing
- Forgetting SelectValue inside SelectTrigger
- Using Select when the list is large (use Combobox)
- Putting non-SelectItem children inside SelectContent
Accessibility
Full keyboard nav: arrow keys, Home, End, typeahead, Escape to close. Radix handles role='combobox' on trigger, role='listbox' on content, aria-selected on items.
Related components
Token budget: 800
Verified against @hex-core/components@1.12.0