- Accordion
- Alert
- Alert Dialog
- Autocomplete
- Avatar
- Badge
- Breadcrumb
- Button
- Card
- Checkbox
- Checkbox Group
- Collapsible
- Combobox
- Dialog
- Empty
- Field
- Fieldset
- Form
- Frame
- Group
- Input
- Label
- Menu
- Meter
- Number Field
- Pagination
- Popover
- Preview Card
- Progress
- Radio Group
- Scroll Area
- Select
- Separator
- Sheet
- Skeleton
- Slider
- Switch
- Table
- Tabs
- Textarea
- Toast
- Toggle
- Toggle Group
- Toolbar
- Tooltip
Select
A common form component for choosing a predefined value in a dropdown menu.
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const items = [
{ label: "Select framework", value: null },
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
]
export default function SelectDemo() {
return (
<Select items={items} defaultValue="next">
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectPopup>
{items.map(({ label, value }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectPopup>
</Select>
)
}
Installation
npx love-ui@latest add selectUsage
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"const items = [
{ label: "Select framework", value: null },
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
]
<Select items={items}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectPopup>
{items.map((item) => (
<SelectItem key={item.value} value={item}>
{item.label}
</SelectItem>
))}
</SelectPopup>
</Select>Examples
For accessible labelling and validation, prefer using the Field component to wrap selects. See the related example: Select field.
Small Size
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const items = [
{ label: "Select framework", value: null },
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
]
export default function SelectSm() {
return (
<Select items={items}>
<SelectTrigger size="sm">
<SelectValue />
</SelectTrigger>
<SelectPopup>
{items.map(({ label, value }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectPopup>
</Select>
)
}
Large Size
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const items = [
{ label: "Select framework", value: null },
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
]
export default function SelectLg() {
return (
<Select items={items}>
<SelectTrigger size="lg">
<SelectValue />
</SelectTrigger>
<SelectPopup>
{items.map(({ label, value }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectPopup>
</Select>
)
}
Disabled
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const items = [
{ label: "Select framework", value: null },
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
]
export default function SelectDisabled() {
return (
<Select items={items}>
<SelectTrigger disabled>
<SelectValue />
</SelectTrigger>
<SelectPopup>
{items.map(({ label, value }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectPopup>
</Select>
)
}
Without Item Alignment
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const items = [
{ label: "Select framework", value: null },
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
]
export default function SelectWithoutAlignment() {
return (
<Select items={items}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectPopup alignItemWithTrigger={false}>
{items.map(({ label, value }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectPopup>
</Select>
)
}
With Groups
import {
Select,
SelectGroup,
SelectGroupLabel,
SelectItem,
SelectPopup,
SelectSeparator,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const placeholder = [{ label: "Select framework", value: null }]
const frontend = [
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
]
const backend = [
{ label: "Express", value: "express" },
{ label: "NestJS", value: "nestjs" },
{ label: "Fastify", value: "fastify" },
{ label: "Django", value: "django" },
{ label: "Flask", value: "flask" },
{ label: "Rails", value: "rails" },
]
export default function SelectWithGroups() {
return (
<Select items={[...placeholder, ...frontend, ...backend]}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectPopup>
<SelectGroup>
<SelectGroupLabel>Frontend</SelectGroupLabel>
{frontend.map(({ label, value }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectGroup>
<SelectSeparator />
<SelectGroup>
<SelectGroupLabel>Backend</SelectGroupLabel>
{backend.map(({ label, value }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectGroup>
</SelectPopup>
</Select>
)
}
Multiple Selection
"use client"
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const languages = {
javascript: "JavaScript",
typescript: "TypeScript",
python: "Python",
java: "Java",
csharp: "C#",
php: "PHP",
cpp: "C++",
rust: "Rust",
go: "Go",
swift: "Swift",
}
type Language = keyof typeof languages
const values = Object.keys(languages) as Language[]
function renderValue(value: Language[]) {
if (value.length === 0) {
return "Select languages…"
}
const firstLanguage = value[0] ? languages[value[0]] : ""
const additionalLanguages =
value.length > 1 ? ` (+${value.length - 1} more)` : ""
return firstLanguage + additionalLanguages
}
export default function SelectMultiple() {
return (
<Select multiple defaultValue={["javascript", "typescript"]}>
<SelectTrigger>
<SelectValue>{renderValue}</SelectValue>
</SelectTrigger>
<SelectPopup alignItemWithTrigger={false}>
{values.map((value) => (
<SelectItem key={value} value={value}>
{languages[value]}
</SelectItem>
))}
</SelectPopup>
</Select>
)
}
Form Integration
"use client"
import * as React from "react"
import { Button } from "@/components/ui/button"
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from "@/components/ui/field"
import { Form } from "@/components/ui/form"
import {
Select,
SelectItem,
SelectPopup,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const items = [
{ label: "Select a framework", value: null },
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
]
export default function SelectForm() {
const [loading, setLoading] = React.useState(false)
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
const formData = new FormData(e.currentTarget)
setLoading(true)
await new Promise((r) => setTimeout(r, 800))
setLoading(false)
alert(`Framework: ${formData.get("framework") || ""}`)
}
return (
<Form onSubmit={onSubmit} className="max-w-64">
<Field>
<FieldLabel>Framework</FieldLabel>
<Select name="framework" items={items} disabled={loading} required>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectPopup>
{items.map(({ label, value }) => (
<SelectItem key={value} value={value}>
{label}
</SelectItem>
))}
</SelectPopup>
</Select>
<FieldDescription>Pick your favorite.</FieldDescription>
<FieldError>Please select a value.</FieldError>
</Field>
<Button type="submit" disabled={loading}>
Submit
</Button>
</Form>
)
}
Comparing with Radix / shadcn
If you’re already familiar with Radix UI and shadcn/ui, this guide highlights the small differences and similarities so you can get started with loveui quickly.
Important: Base UI changes how options are provided. Instead of deriving options from children only (Radix pattern), you should pass an items prop (array or record) so values and labels are known before hydration. This avoids SSR pitfalls and improves mount performance. Alternatively, provide a function child to SelectValue to format the label. See the Base UI Select docs.
Prop Mapping
| Component | Radix UI Prop | Base UI Prop |
|---|---|---|
Select | items | items |
SelectValue | placeholder | removed |
SelectPopup | alignItemWithTrigger | no equivalent |
Quick Checklist
- Set
itemsprop onSelect - Remove
placeholderfromSelect - Prefer
SelectPopupinstead ofSelectContent - If you used
asChildon parts, switch to therenderprop
Additional Notes
Size Comparison
loveui select sizes are more compact compared to shadcn/ui, making them better suited for dense applications:
| Size | Height (shadcn/ui) | Height (loveui) |
|---|---|---|
sm | 32px | 28px |
default | 36px | 32px |
lg | - | 36px |
So, for example, if you were using the default size in shadcn/ui and you want to preserve the original height, you should use the lg size in loveui.
New Prop
Base UI introduces the alignItemWithTrigger prop to control whether the SelectContent overlaps the SelectTrigger so the selected item's text is aligned with the trigger's value text.
Comparison Example
<Select>
<SelectTrigger>
<SelectValue placeholder="Select a framework" />
</SelectTrigger>
<SelectContent>
<SelectItem value="next">Next.js</SelectItem>
<SelectItem value="vite">Vite</SelectItem>
<SelectItem value="astro">Astro</SelectItem>
</SelectContent>
</Select><Select
items={[
{ label: "Select a framework", value: null },
{ label: "Next.js", value: "next" },
{ label: "Vite", value: "vite" },
{ label: "Astro", value: "astro" },
]}
>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectPopup alignItemWithTrigger={false}>
{items.map((item) => (
<SelectItem key={item.value} value={item}>
{item.label}
</SelectItem>
))}
</SelectPopup>
</Select>