Shadcn.io is not affiliated with official shadcn/ui
Shadcn Select React select for custom dropdown menus with search, grouping, and keyboard navigation. Built with TypeScript and Tailwind CSS for Next.js using Radix UI.
That dropdown where you pick one option from a list? That's what a select does. But unlike the boring native select, this one actually looks good, works with your keyboard, and doesn't make you want to cry when you try to style it. This shadcn/ui select brings proper dropdown experiences to your React applications.
Clean dropdown with grouped options:
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, } from "~/components/ui/select" export default function SelectDemo() { return ( <div className="flex justify-center self-start pt-6 w-full" style={{ all: "revert", display: "flex", justifyContent: "center", alignSelf: "flex-start", paddingTop: "1.5rem", width: "100%", fontSize: "14px", lineHeight: "1.5", letterSpacing: "normal", }} > <Select> <SelectTrigger className="w-[180px]"> <SelectValue placeholder="Select a fruit" /> </SelectTrigger> <SelectContent> <SelectGroup> <SelectLabel>Fruits</SelectLabel> <SelectItem value="apple">Apple</SelectItem> <SelectItem value="banana">Banana</SelectItem> <SelectItem value="blueberry">Blueberry</SelectItem> <SelectItem value="grapes">Grapes</SelectItem> <SelectItem value="pineapple">Pineapple</SelectItem> </SelectGroup> </SelectContent> </Select> </div> ) }
Built on Radix UI Select with full keyboard navigation, typeahead search, and proper ARIA support. This free open source component handles all the complex positioning and focus management while looking exactly how you want. Styled with Tailwind CSS to match your design system instead of fighting browser default styling.
npx shadcn@latest add select
Here's the thing—native selects are basically the IE6 of form controls. They look different on every platform, can't be styled consistently, and offer zero customization options. Think about how frustrating it is when your beautifully designed form suddenly has an ugly system dropdown that doesn't match anything else.
Custom selects solve everything that's wrong with native ones. You get consistent styling across all browsers and devices, typeahead search for long lists, grouped options that actually make sense, and the ability to add icons, descriptions, or any content you need. Plus they work perfectly with keyboard navigation and screen readers.
This free shadcn select handles the complex parts—positioning magic, focus management, portal rendering, keyboard navigation—while you focus on creating dropdown experiences that actually enhance your forms. Whether you're building country selectors, status pickers, or preference settings in your Next.js applications, selects that work intuitively improve user experience in your JavaScript projects.
With flags and search for long lists:
"use client" import * as React from "react" import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, } from "~/components/ui/select" const countries = [ { value: "us", label: "United States", flag: "🇺🇸" }, { value: "ca", label: "Canada", flag: "🇨🇦" }, { value: "gb", label: "United Kingdom", flag: "🇬🇧" }, { value: "de", label: "Germany", flag: "🇩🇪" }, { value: "fr", label: "France", flag: "🇫🇷" }, { value: "it", label: "Italy", flag: "🇮🇹" }, { value: "es", label: "Spain", flag: "🇪🇸" }, { value: "au", label: "Australia", flag: "🇦🇺" }, { value: "jp", label: "Japan", flag: "🇯🇵" }, { value: "kr", label: "South Korea", flag: "🇰🇷" }, { value: "sg", label: "Singapore", flag: "🇸🇬" }, { value: "br", label: "Brazil", flag: "🇧🇷" }, { value: "mx", label: "Mexico", flag: "🇲🇽" }, ] const regions = { "North America": ["us", "ca", "mx"], Europe: ["gb", "de", "fr", "it", "es"], "Asia Pacific": ["au", "jp", "kr", "sg"], "South America": ["br"], } export default function SelectCountry() { const [selected, setSelected] = React.useState("") const selectedCountry = countries.find(country => country.value === selected) return ( <div className="flex justify-center self-start pt-6 w-full" style={{ all: "revert", display: "flex", justifyContent: "center", alignSelf: "flex-start", paddingTop: "1.5rem", width: "100%", fontSize: "14px", lineHeight: "1.5", letterSpacing: "normal", }} > <Select onValueChange={setSelected} value={selected}> <SelectTrigger className="w-[250px]"> <SelectValue placeholder="Select a country"> {selectedCountry && ( <div className="flex items-center gap-2"> <span>{selectedCountry.flag}</span> <span>{selectedCountry.label}</span> </div> )} </SelectValue> </SelectTrigger> <SelectContent> {Object.entries(regions).map(([region, countryCodes]) => ( <SelectGroup key={region}> <SelectLabel>{region}</SelectLabel> {countryCodes.map(code => { const country = countries.find(c => c.value === code)! return ( <SelectItem key={code} value={code}> <div className="flex items-center gap-2"> <span>{country.flag}</span> <span>{country.label}</span> </div> </SelectItem> ) })} </SelectGroup> ))} </SelectContent> </Select> </div> ) }
Visual indicators for different states:
"use client" import * as React from "react" import { Badge } from "~/components/ui/badge" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "~/components/ui/select" import { cn } from "~/lib/utils" const statuses = [ { value: "draft", label: "Draft", color: "bg-gray-100 text-gray-800", icon: "⭕", }, { value: "pending", label: "Pending Review", color: "bg-yellow-100 text-yellow-800", icon: "⏳", }, { value: "approved", label: "Approved", color: "bg-green-100 text-green-800", icon: "✅", }, { value: "rejected", label: "Rejected", color: "bg-red-100 text-red-800", icon: "❌", }, { value: "published", label: "Published", color: "bg-blue-100 text-blue-800", icon: "🌐", }, { value: "archived", label: "Archived", color: "bg-gray-100 text-gray-600", icon: "📦", }, ] export default function SelectStatus() { const [selected, setSelected] = React.useState("draft") const selectedStatus = statuses.find(status => status.value === selected) return ( <div className="flex justify-center self-start pt-6 w-full" style={{ all: "revert", display: "flex", justifyContent: "center", alignSelf: "flex-start", paddingTop: "1.5rem", width: "100%", fontSize: "14px", lineHeight: "1.5", letterSpacing: "normal", }} > <div className="w-full max-w-sm space-y-2"> <label className="text-sm font-medium" htmlFor="document-status"> Document Status </label> <Select onValueChange={setSelected} value={selected}> <SelectTrigger className="w-full" id="document-status"> <SelectValue> {selectedStatus && ( <div className="flex items-center gap-2"> <span>{selectedStatus.icon}</span> <Badge className={cn("text-xs", selectedStatus.color)} variant="secondary"> {selectedStatus.label} </Badge> </div> )} </SelectValue> </SelectTrigger> <SelectContent> {statuses.map(status => ( <SelectItem key={status.value} value={status.value}> <div className="flex items-center gap-2 w-full"> <span>{status.icon}</span> <Badge className={cn("text-xs", status.color)} variant="secondary"> {status.label} </Badge> </div> </SelectItem> ))} </SelectContent> </Select> <p className="text-xs text-muted-foreground">Change the status of this document</p> </div> </div> ) }
Integrated with react-hook-form and validation:
"use client" import { zodResolver } from "@hookform/resolvers/zod" import Link from "next/link" import { useForm } from "react-hook-form" import { toast } from "sonner" import { z } from "zod" import { Button } from "~/components/ui/button" import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "~/components/ui/form" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "~/components/ui/select" const FormSchema = z.object({ email: z .string({ required_error: "Please select an email to display.", }) .email(), }) export default function SelectForm() { const form = useForm<z.infer<typeof FormSchema>>({ resolver: zodResolver(FormSchema), }) function onSubmit(data: z.infer<typeof FormSchema>) { toast("You submitted the following values", { description: ( <pre className="mt-2 w-[320px] rounded-md bg-neutral-950 p-4"> <code className="text-white">{JSON.stringify(data, null, 2)}</code> </pre> ), }) } return ( <div className="flex justify-center self-start pt-6 w-full" style={{ all: "revert", display: "flex", justifyContent: "center", alignSelf: "flex-start", paddingTop: "1.5rem", width: "100%", fontSize: "14px", lineHeight: "1.5", letterSpacing: "normal", }} > <Form {...(form as any)}> <form className="w-2/3 space-y-6" onSubmit={form.handleSubmit(onSubmit)}> <FormField control={form.control as any} name="email" render={({ field }) => ( <FormItem> <FormLabel>Email</FormLabel> <Select defaultValue={field.value} onValueChange={field.onChange}> <FormControl> <SelectTrigger> <SelectValue placeholder="Select a verified email to display" /> </SelectTrigger> </FormControl> <SelectContent> <SelectItem value="[email protected] ">[email protected] </SelectItem> <SelectItem value="[email protected] ">[email protected] </SelectItem> <SelectItem value="[email protected] ">[email protected] </SelectItem> </SelectContent> </Select> <FormDescription> You can manage email addresses in your{" "} <Link className="underline" href="#" onClick={e => e.preventDefault()}> email settings </Link> . </FormDescription> <FormMessage /> </FormItem> )} /> <Button type="submit">Submit</Button> </form> </Form> </div> ) }
When you need to show more information:
"use client" import * as React from "react" import { Badge } from "~/components/ui/badge" import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, } from "~/components/ui/select" const teamMembers = [ { value: "sarah", name: "Sarah Chen", role: "Frontend Developer", avatar: "👩💻", status: "available", }, { value: "mike", name: "Mike Johnson", role: "Backend Engineer", avatar: "👨💼", status: "busy", }, { value: "emma", name: "Emma Wilson", role: "UI/UX Designer", avatar: "🎨", status: "available", }, { value: "alex", name: "Alex Rodriguez", role: "DevOps Engineer", avatar: "⚙️", status: "away", }, { value: "lisa", name: "Lisa Park", role: "Product Manager", avatar: "📋", status: "available", }, { value: "david", name: "David Kim", role: "QA Engineer", avatar: "🔍", status: "busy", }, ] const statusColors = { available: "bg-green-100 text-green-800", busy: "bg-red-100 text-red-800", away: "bg-yellow-100 text-yellow-800", } const departments = { Engineering: ["sarah", "mike", "alex", "david"], "Design & Product": ["emma", "lisa"], } export default function SelectMultiColumn() { const [selected, setSelected] = React.useState("") const selectedMember = teamMembers.find(member => member.value === selected) return ( <div className="flex justify-center self-start pt-6 w-full" style={{ all: "revert", display: "flex", justifyContent: "center", alignSelf: "flex-start", paddingTop: "1.5rem", width: "100%", fontSize: "14px", lineHeight: "1.5", letterSpacing: "normal", }} > <div className="w-full max-w-sm space-y-2"> <label className="text-sm font-medium" htmlFor="team-member-select"> Assign to Team Member </label> <Select onValueChange={setSelected} value={selected}> <SelectTrigger className="w-full" id="team-member-select"> <SelectValue placeholder="Select team member"> {selectedMember && ( <div className="flex items-center gap-3"> <span className="text-lg">{selectedMember.avatar}</span> <div className="flex flex-col items-start"> <span className="font-medium">{selectedMember.name}</span> <span className="text-xs text-muted-foreground">{selectedMember.role}</span> </div> </div> )} </SelectValue> </SelectTrigger> <SelectContent className="w-80"> {Object.entries(departments).map(([dept, memberIds]) => ( <SelectGroup key={dept}> <SelectLabel className="font-semibold">{dept}</SelectLabel> {memberIds.map(id => { const member = teamMembers.find(m => m.value === id)! return ( <SelectItem className="h-auto py-3" key={id} value={id}> <div className="flex items-center gap-3 w-full"> <span className="text-lg">{member.avatar}</span> <div className="flex-1 flex items-center justify-between"> <div className="flex flex-col items-start"> <span className="font-medium">{member.name}</span> <span className="text-sm text-muted-foreground">{member.role}</span> </div> <Badge className={`text-xs capitalize ${statusColors[member.status as keyof typeof statusColors]}`} variant="secondary" > {member.status} </Badge> </div> </div> </SelectItem> ) })} </SelectGroup> ))} </SelectContent> </Select> <p className="text-xs text-muted-foreground">Choose a team member to assign this task</p> </div> </div> ) }
Organized options with clear grouping:
import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue, } from "~/components/ui/select" export default function SelectDemo() { return ( <div className="flex justify-center self-start pt-6 w-full" style={{ all: "revert", display: "flex", justifyContent: "center", alignSelf: "flex-start", paddingTop: "1.5rem", width: "100%", fontSize: "14px", lineHeight: "1.5", letterSpacing: "normal", }} > <Select> <SelectTrigger className="w-[180px]"> <SelectValue placeholder="Select a fruit" /> </SelectTrigger> <SelectContent> <SelectGroup> <SelectLabel>Fruits</SelectLabel> <SelectItem value="apple">Apple</SelectItem> <SelectItem value="banana">Banana</SelectItem> <SelectItem value="blueberry">Blueberry</SelectItem> <SelectItem value="grapes">Grapes</SelectItem> <SelectItem value="pineapple">Pineapple</SelectItem> </SelectGroup> </SelectContent> </Select> </div> ) }
This free open source select component includes everything you need:
TypeScript-first - Full type safety with selection events and state management
Radix UI powered - Battle-tested positioning, focus management, and accessibility
Typeahead search - Start typing to jump to options instantly
Tailwind CSS styled - Customize with utilities, not fighting browser defaults
Keyboard accessible - Arrow keys, Enter, Escape, Page Up/Down all work perfectly
Grouped options - Organize long lists with labels and visual separation
Portal rendering - No z-index nightmares or positioning issues
Touch optimized - Works great on mobile without weird native pickers
Component Purpose Key Props SelectRoot container value, onValueChange, open, disabledSelectTriggerButton that opens Styling and interaction props SelectContentDropdown panel position, side, align for placementSelectItemIndividual option value, disabled, textValue for search
Prop Type Purpose valuestring Currently selected value defaultValuestring Initial selection for uncontrolled use onValueChangefunction Callback when selection changes placeholderstring Text shown when nothing selected
Component Use Case Purpose SelectGroupOrganized options Container for related items SelectLabelSection headers Visual grouping with labels SelectSeparatorVisual breaks Divide sections clearly
Set consistent widths and don't let selects jump around. This free shadcn/ui select component adapts to content, but unpredictable width changes frustrate users. This TypeScript component handles the dropdown mechanics—you provide consistent trigger widths that maintain layout stability in your Next.js applications.
Use placeholders that actually help users understand the choice. "Select option" tells users nothing—"Choose your country" or "Pick a status" provides context. This open source shadcn select shows whatever placeholder text you provide—make it descriptive enough that users understand what they're selecting.
Group related options logically and keep scanning easy. Long lists become overwhelming without organization. This JavaScript component supports visual grouping with labels and separators—use them to create logical categories that match how users think about the options.
Handle long option lists with typeahead search and smart defaults. When you have dozens of options, users should be able to type to find what they need quickly. The Tailwind CSS styled component includes built-in typeahead—organize options alphabetically or by usage frequency to make searching intuitive.
Test keyboard navigation thoroughly and make it feel natural. Power users rely on keyboard shortcuts for form efficiency. This component handles arrow keys, Enter, Escape, and typeahead automatically—ensure your option order and grouping support smooth keyboard-only interaction patterns.
Selects naturally work with Form components for validation and error handling in your React applications. Use Button components for consistent styling when selects appear alongside other form controls.
For complex interfaces, combine selects with Popover components for additional context or Command components when you need more advanced search functionality. This open source pattern creates powerful filtering and selection experiences.
When building data-heavy applications, pair selects with DataTable components for column filtering or Badge components to show selected filter states. Label components ensure proper accessibility associations with select triggers.
For multi-step workflows, use selects with Dialog components for settings panels or Sheet components for mobile-friendly option selection. Your JavaScript application can compose these shadcn components while maintaining consistent interaction patterns across different contexts.