Forms
Select.
A dropdown for picking one value from a list, with groups, labels, icons and full keyboard support.
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
export default function SelectDemo() {
return (
<Select>
<SelectTrigger className="w-full max-w-60" aria-label="Site theme">
<SelectValue placeholder="Pick a theme" />
</SelectTrigger>
<SelectContent>
<SelectItem value="lantern">Lantern</SelectItem>
<SelectItem value="moss">Moss</SelectItem>
<SelectItem value="amber">Amber terminal</SelectItem>
<SelectItem value="paper">Paper</SelectItem>
</SelectContent>
</Select>
);
}Installation#
With the CLI
$npx lanterncn add selectThis also installs the Lantern theme and any Lantern UI components it depends on. The shadcn CLI works directly too: npx shadcn@latest add httptim/lantern-ui/select. See Installation if your project is not set up yet.
Manually
Install the dependencies
npm install radix-ui lucide-reactCopy the source into your project
components/ui/select.tsx"use client"; import * as React from "react"; import { Select as SelectPrimitive } from "radix-ui"; import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"; import { cn } from "@/lib/utils"; function Select({ ...props }: React.ComponentProps<typeof SelectPrimitive.Root>) { return <SelectPrimitive.Root data-slot="select" {...props} />; } function SelectGroup({ ...props }: React.ComponentProps<typeof SelectPrimitive.Group>) { return <SelectPrimitive.Group data-slot="select-group" {...props} />; } function SelectValue({ ...props }: React.ComponentProps<typeof SelectPrimitive.Value>) { return <SelectPrimitive.Value data-slot="select-value" {...props} />; } function SelectTrigger({ className, size = "default", children, ...props }: React.ComponentProps<typeof SelectPrimitive.Trigger> & { size?: "sm" | "default" }) { return ( <SelectPrimitive.Trigger data-slot="select-trigger" data-size={size} className={cn( "flex w-fit cursor-pointer items-center justify-between gap-2 rounded-md border border-input bg-background/40 px-3 py-2 text-sm whitespace-nowrap text-foreground transition-[border-color,box-shadow] outline-none data-[size=default]:h-10 data-[size=sm]:h-8 data-[size=sm]:text-xs", "hover:border-muted-foreground/60 focus-visible:border-primary focus-visible:ring-2 focus-visible:ring-ring/25 data-[state=open]:border-primary", "aria-invalid:border-destructive aria-invalid:ring-destructive/20 disabled:cursor-not-allowed disabled:opacity-50 data-[placeholder]:text-muted-foreground/70", "*: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} <SelectPrimitive.Icon asChild> <ChevronDownIcon className="size-4 opacity-80" /> </SelectPrimitive.Icon> </SelectPrimitive.Trigger> ); } function SelectContent({ className, children, position = "popper", align = "center", ...props }: React.ComponentProps<typeof SelectPrimitive.Content>) { return ( <SelectPrimitive.Portal> <SelectPrimitive.Content data-slot="select-content" className={cn( "relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover text-popover-foreground shadow-block-sm", "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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", 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", className, )} position={position} align={align} {...props} > <SelectScrollUpButton /> <SelectPrimitive.Viewport className={cn( "p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1", )} > {children} </SelectPrimitive.Viewport> <SelectScrollDownButton /> </SelectPrimitive.Content> </SelectPrimitive.Portal> ); } function SelectLabel({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Label>) { return ( <SelectPrimitive.Label data-slot="select-label" className={cn("px-2 pt-2 pb-1.5 font-mono text-[10px] tracking-[0.2em] text-success uppercase", className)} {...props} /> ); } function SelectItem({ className, children, ...props }: React.ComponentProps<typeof SelectPrimitive.Item>) { return ( <SelectPrimitive.Item data-slot="select-item" className={cn( "relative flex w-full cursor-default items-center gap-2 rounded-sm py-2 pr-8 pl-2 text-sm outline-hidden select-none focus:bg-secondary focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-40 data-[state=checked]:text-primary", "[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2", className, )} {...props} > <span className="absolute right-2 flex size-3.5 items-center justify-center"> <SelectPrimitive.ItemIndicator> <CheckIcon className="size-4 text-primary" /> </SelectPrimitive.ItemIndicator> </span> <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText> </SelectPrimitive.Item> ); } function SelectSeparator({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.Separator>) { return ( <SelectPrimitive.Separator data-slot="select-separator" className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)} {...props} /> ); } function SelectScrollUpButton({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) { return ( <SelectPrimitive.ScrollUpButton data-slot="select-scroll-up-button" className={cn("flex cursor-default items-center justify-center py-1 text-muted-foreground", className)} {...props} > <ChevronUpIcon className="size-4" /> </SelectPrimitive.ScrollUpButton> ); } function SelectScrollDownButton({ className, ...props }: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) { return ( <SelectPrimitive.ScrollDownButton data-slot="select-scroll-down-button" className={cn("flex cursor-default items-center justify-center py-1 text-muted-foreground", className)} {...props} > <ChevronDownIcon className="size-4" /> </SelectPrimitive.ScrollDownButton> ); } export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, };Update the import paths to match your project
The source imports
cnfrom@/lib/utils.
Usage#
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";<Select>
<SelectTrigger className="w-48">
<SelectValue placeholder="Pick a theme" />
</SelectTrigger>
<SelectContent>
<SelectItem value="lantern">Lantern</SelectItem>
<SelectItem value="moss">Moss</SelectItem>
</SelectContent>
</Select>Examples#
Groups and icons
import { CpuIcon, MonitorIcon, TabletSmartphoneIcon } from "lucide-react";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
export default function SelectGrouped() {
return (
<div className="grid w-full max-w-60 gap-2">
<Label htmlFor="sel-host">Host computer</Label>
<Select defaultValue="base-81">
<SelectTrigger id="sel-host" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectLabel>Computers</SelectLabel>
<SelectItem value="base-81">
<MonitorIcon />
Base #81
</SelectItem>
<SelectItem value="library-12">
<MonitorIcon />
Library #12
</SelectItem>
</SelectGroup>
<SelectSeparator />
<SelectGroup>
<SelectLabel>Turtles</SelectLabel>
<SelectItem value="miner-3">
<CpuIcon />
Miner #3
</SelectItem>
<SelectItem value="farmer-7" disabled>
<CpuIcon />
Farmer #7 (offline)
</SelectItem>
</SelectGroup>
<SelectSeparator />
<SelectGroup>
<SelectLabel>Pocket</SelectLabel>
<SelectItem value="pocket-2">
<TabletSmartphoneIcon />
Pocket #2
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</div>
);
}Invalid, disabled and small
Pick where your host computer lives.
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
export default function SelectStates() {
return (
<div className="grid w-full max-w-60 gap-5">
<div className="grid gap-2">
<Label htmlFor="sel-region">Region</Label>
<Select>
<SelectTrigger id="sel-region" aria-invalid className="w-full">
<SelectValue placeholder="Choose a region" />
</SelectTrigger>
<SelectContent>
<SelectItem value="overworld">Overworld</SelectItem>
<SelectItem value="nether">Nether</SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-destructive">Pick where your host computer lives.</p>
</div>
<div className="grid gap-2">
<Label htmlFor="sel-plan">Plan</Label>
<Select defaultValue="free" disabled>
<SelectTrigger id="sel-plan" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="free">Free</SelectItem>
</SelectContent>
</Select>
</div>
<Select defaultValue="10">
<SelectTrigger size="sm" aria-label="Rows per page" className="w-24">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="10">10 rows</SelectItem>
<SelectItem value="25">25 rows</SelectItem>
<SelectItem value="50">50 rows</SelectItem>
</SelectContent>
</Select>
</div>
);
}