UI/UI
Select
A comprehensive select component with support for single selection, icons, and animations.
<Select>
<SelectTrigger>
<SelectValue placeholder="Select an option" />
</SelectTrigger>
<SelectContent>
<SelectItem value="apple">Apple</SelectItem>
<SelectItem value="banana">Banana</SelectItem>
<SelectItem value="cherry">Cherry</SelectItem>
<SelectItem value="date">Date</SelectItem>
<SelectItem value="elderberry">Elderberry</SelectItem>
</SelectContent>
</Select>
Installation
Install following dependencies:
npm install @radix-ui/react-select class-variance-authority lucide-react motion
pnpm add @radix-ui/react-select class-variance-authority lucide-react motion
yarn add @radix-ui/react-select class-variance-authority lucide-react motion
bun add @radix-ui/react-select class-variance-authority lucide-react motion
Copy and paste the following code into your project.
"use client";
import * as React from "react";
import * as SelectPrimitive from "@radix-ui/react-select";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { type LucideIcon, ChevronDown, Check } from "lucide-react";
import { motion, AnimatePresence } from "motion/react";
const selectTriggerVariants = cva(
"flex h-9 w-full items-center justify-between gap-3 rounded-[var(--radius)] border border-[hsl(var(--hu-border))] bg-[hsl(var(--hu-background))] px-3 py-2 text-sm transition-all placeholder:text-[hsl(var(--hu-muted-foreground))] focus:outline-none focus:ring-2 focus:ring-[hsl(var(--hu-ring))] focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1 border border-[hsl(var(--hu-border))] bg-[hsl(var(--hu-input))]",
{
variants: {
variant: {
default:
"hover:bg-[hsl(var(--hu-accent))] hover:text-[hsl(var(--hu-accent-foreground))]",
outline: "border-2 hover:border-[hsl(var(--hu-ring))]",
ghost:
"border-transparent hover:bg-[hsl(var(--hu-accent))] hover:text-[hsl(var(--hu-accent-foreground))]",
},
size: {
sm: "h-8 p-2 text-xs gap-2",
default: "h-9 p-3 gap-3",
lg: "h-10 p-4 text-base gap-4",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
);
const selectContentVariants = cva(
"relative z-50 max-h-[300px] min-w-[8rem] overflow-hidden rounded-[var(--radius)] border border-[hsl(var(--hu-border))] bg-[hsl(var(--hu-background))] text-[hsl(var(--hu-foreground))] shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
{
variants: {
position: {
popper:
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
"item-aligned": "",
},
},
defaultVariants: {
position: "popper",
},
}
);
const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group;
const SelectValue = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Value>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Value> & {
placeholder?: string;
}
>(({ className, placeholder, ...props }, ref) => (
<SelectPrimitive.Value
ref={ref}
className={cn("text-sm select-none", className)}
placeholder={
placeholder && (
<span className="text-[hsl(var(--hu-muted-foreground))] select-none">
{placeholder}
</span>
)
}
{...props}
/>
));
SelectValue.displayName = SelectPrimitive.Value.displayName;
interface SelectTriggerProps
extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>,
VariantProps<typeof selectTriggerVariants> {
icon?: LucideIcon;
placeholder?: string;
}
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
SelectTriggerProps
>(
(
{ className, children, variant, size, icon: Icon, placeholder, ...props },
ref
) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"group",
selectTriggerVariants({ variant, size }),
className
)}
{...props}
>
<div className="flex items-center gap-2 flex-1 min-w-0">
{Icon && (
<Icon
size={16}
className="shrink-0 text-[hsl(var(--hu-muted-foreground))]"
/>
)}
<span className="truncate">{children}</span>
</div>{" "}
<SelectPrimitive.Icon asChild>
<ChevronDown
size={16}
className="opacity-50 shrink-0 transition-transform duration-200 group-data-[state=open]:rotate-180"
/>
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
)
);
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
interface SelectContentProps
extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> {
position?: "popper" | "item-aligned";
}
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
SelectContentProps
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(selectContentVariants({ position }), className)}
position={position}
{...props}
>
<motion.div
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
transition={{ duration: 0.15 }}
>
<SelectPrimitive.Viewport
className={cn(
"p-2 max-h-[280px] overflow-y-auto scrollbar-thin scrollbar-thumb-[hsl(var(--hu-border))] scrollbar-track-transparent",
position === "popper" &&
"h-fit w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
</motion.div>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
));
SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn(
"px-3 py-2 text-xs font-semibold text-[hsl(var(--hu-muted-foreground))]",
className
)}
{...props}
/>
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
interface SelectItemProps
extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item> {
icon?: LucideIcon;
}
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
SelectItemProps
>(({ className, children, icon: Icon, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-lg py-2 pl-3 pr-8 text-sm outline-none focus:bg-[hsl(var(--hu-accent))] focus:text-[hsl(var(--hu-accent-foreground))] data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[disabled]:text-[hsl(var(--hu-muted-foreground))]",
className
)}
{...props}
>
<motion.div
className="flex w-full items-center gap-2"
whileHover={{ x: 2 }}
transition={{ duration: 0.1 }}
>
{Icon && <Icon size={16} className="shrink-0" />}
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</motion.div>
<span className="absolute right-3 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<motion.div
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ duration: 0.1 }}
>
<Check size={16} />
</motion.div>
</SelectPrimitive.ItemIndicator>
</span>
</SelectPrimitive.Item>
));
SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-[hsl(var(--hu-muted))]", className)}
{...props}
/>
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
selectTriggerVariants,
};
npx hextaui@latest add select
pnpm dlx hextaui@latest add select
yarn dlx hextaui@latest add select
bun x hextaui@latest add select
Usage
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/Select";
<Select>
<SelectTrigger>
<SelectValue placeholder="Select an option" />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">Option 1</SelectItem>
<SelectItem value="option2">Option 2</SelectItem>
<SelectItem value="option3">Option 3</SelectItem>
</SelectContent>
</Select>
Examples
With Icon
import { User } from "lucide-react";
<Select>
<SelectTrigger icon={User}>
<SelectValue placeholder="Select a user" />
</SelectTrigger>
<SelectContent>
<SelectItem value="john" icon={User}>
John Doe
</SelectItem>
<SelectItem value="jane" icon={User}>
Jane Smith
</SelectItem>
<SelectItem value="bob" icon={User}>
Bob Johnson
</SelectItem>
</SelectContent>
</Select>
With Groups
import { Mail, Phone, MapPin, Home } from "lucide-react";
<Select>
<SelectTrigger>
<SelectValue placeholder="Select contact method" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Digital</SelectLabel>
<SelectItem value="email" icon={Mail}>
Email
</SelectItem>
<SelectItem value="phone" icon={Phone}>
Phone
</SelectItem>
</SelectGroup>
<SelectSeparator />
<SelectGroup>
<SelectLabel>Physical</SelectLabel>
<SelectItem value="mail" icon={MapPin}>
Mail
</SelectItem>
<SelectItem value="visit" icon={Home}>
In Person
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
Variants
<div className="space-y-4">
{/* Default */}
<Select>
<SelectTrigger placeholder="Default variant">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">Option 1</SelectItem>
<SelectItem value="option2">Option 2</SelectItem>
</SelectContent>
</Select>
{/* Outline */}
<Select>
<SelectTrigger variant="outline" placeholder="Outline variant">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">Option 1</SelectItem>
<SelectItem value="option2">Option 2</SelectItem>
</SelectContent>
</Select>
{/* Ghost */}
<Select>
<SelectTrigger variant="ghost" placeholder="Ghost variant">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">Option 1</SelectItem>
<SelectItem value="option2">Option 2</SelectItem>
</SelectContent>
</Select>
</div>
Sizes
<div className="space-y-4">
{/* Small */}
<Select>
<SelectTrigger size="sm">
<SelectValue placeholder="Small select" />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">Option 1</SelectItem>
<SelectItem value="option2">Option 2</SelectItem>
</SelectContent>
</Select>
{/* Default */}
<Select>
<SelectTrigger>
<SelectValue placeholder="Default select" />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">Option 1</SelectItem>
<SelectItem value="option2">Option 2</SelectItem>
</SelectContent>
</Select>
{/* Large */}
<Select>
<SelectTrigger size="lg">
<SelectValue placeholder="Large select" />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">Option 1</SelectItem>
<SelectItem value="option2">Option 2</SelectItem>
</SelectContent>
</Select>
</div>
Scrollable Content
import { MapPin } from "lucide-react";
const countries = [
"United States", "Canada", "United Kingdom", "Germany", "France",
"Italy", "Spain", "Netherlands", "Belgium", "Switzerland",
"Austria", "Sweden", "Norway", "Denmark", "Finland",
"Poland", "Czech Republic", "Hungary", "Portugal", "Greece",
// ... more countries
];
<Select>
<SelectTrigger icon={MapPin}>
<SelectValue placeholder="Select a country" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Countries</SelectLabel>
{countries.map((country) => (
<SelectItem key={country} value={country.toLowerCase().replace(/\s+/g, '-')}>
{country}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
With Disabled Options
<Select>
<SelectTrigger>
<SelectValue placeholder="Select an option" />
</SelectTrigger>
<SelectContent>
<SelectItem value="available" icon={Star}>
Available Option
</SelectItem>
<SelectItem value="disabled" icon={Heart} disabled>
Disabled Option
</SelectItem>
<SelectItem value="another" icon={Settings}>
Another Option
</SelectItem>
</SelectContent>
</Select>
Disabled Select
<Select disabled>
<SelectTrigger>
<SelectValue placeholder="This select is disabled" />
</SelectTrigger>
<SelectContent>
<SelectItem value="option1">Option 1</SelectItem>
<SelectItem value="option2">Option 2</SelectItem>
</SelectContent>
</Select>
API Reference
Select
Prop | Type | Default |
---|---|---|
required? | boolean | false |
name? | string | - |
disabled? | boolean | false |
onValueChange? | (value: string) => void | - |
defaultValue? | string | - |
value? | string | - |
SelectTrigger
Prop | Type | Default |
---|---|---|
placeholder? | string | - |
icon? | LucideIcon | - |
size? | "sm" | "default" | "lg" | "default" |
variant? | "default" | "outline" | "ghost" | "default" |
SelectItem
Prop | Type | Default |
---|---|---|
icon? | LucideIcon | - |
disabled? | boolean | false |
value | string | - |
Edit on GitHub
Last updated on