Pick one or more options from a list that opens on the current value, with typeahead, groups and form support.
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const fonts = [
{ value: "inter", label: "Inter" },
{ value: "geist", label: "Geist" },
{ value: "ibm-plex", label: "IBM Plex Sans" },
{ value: "source-serif", label: "Source Serif" },
{ value: "jetbrains", label: "JetBrains Mono" },
]
export function SelectDemo() {
return (
<Select items={fonts} defaultValue="geist">
<SelectTrigger aria-label="Font" className="w-48">
<SelectValue />
</SelectTrigger>
<SelectContent>
{fonts.map((font) => (
<SelectItem key={font.value} value={font.value}>
{font.label}
</SelectItem>
))}
</SelectContent>
</Select>
)
}pnpm dlx shadcn@latest add https://hextaui.com/r/select.jsonAdds the component, the HextaUI theme tokens and any HextaUI components it depends on.
- 1
Add the theme tokens to your global CSS, if you haven’t yet.
- 2
Install the dependencies.
pnpm add @base-ui/react @tabler/icons-react class-variance-authority cn - 3
Copy and paste the following code into your project.
components/ui/select.tsx "use client" import * as React from "react" import { Select as SelectPrimitive } from "@base-ui/react/select" import { IconCheck, IconChevronDown, IconChevronUp, IconSelector, } from "@tabler/icons-react" import { cva } from "class-variance-authority" import { cn } from "cn" type ClassName<State> = string | ((state: State) => string | undefined) | undefined function mergeClassName<State>(base: string, className: ClassName<State>) { return typeof className === "function" ? (state: State) => cn(base, className(state)) : cn(base, className) } const SelectTriggerContext = React.createContext<React.RefObject<HTMLElement | null> | null>(null) function Select<Value, Multiple extends boolean | undefined = false>( props: SelectPrimitive.Root.Props<Value, Multiple> ) { const triggerRef = React.useRef<HTMLElement | null>(null) return ( <SelectTriggerContext.Provider value={triggerRef}> <SelectPrimitive.Root {...props} /> </SelectTriggerContext.Provider> ) } function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) { return ( <SelectPrimitive.Group data-slot="select-group" className={mergeClassName("flex flex-col", className)} {...props} /> ) } function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) { return ( <SelectPrimitive.Value data-slot="select-value" className={mergeClassName( "flex min-w-0 flex-1 items-center gap-2 truncate text-start", className )} {...props} /> ) } const selectTriggerVariants = cva( "group/select-trigger inline-flex w-fit min-w-0 items-center justify-between gap-2 rounded-md bg-transparent text-sm whitespace-nowrap inset-ring-(length:--hairline) inset-ring-input transition-[color,background-color,box-shadow] duration-150 ease-out-cubic outline-none select-none focus-visible:ring-3 focus-visible:ring-focus-ring focus-visible:inset-ring-ring focus-visible:outline-hidden disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:inset-ring-destructive aria-invalid:focus-visible:ring-destructive/70 data-invalid:inset-ring-destructive data-placeholder:text-muted-foreground data-popup-open:inset-ring-ring dark:bg-input/30 dark:hover:bg-input/50 forced-colors:border pointer-coarse:text-[max(16px,1rem)] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [@media(hover:hover)]:hover:bg-muted/50", { variants: { size: { sm: "h-8 ps-2.5 pe-1.5", default: "h-9 ps-3 pe-2", lg: "h-10 ps-3 pe-2.5", }, }, defaultVariants: { size: "default", }, } ) type SelectTriggerProps = SelectPrimitive.Trigger.Props & { size?: "sm" | "default" | "lg" } function SelectTrigger({ className, size = "default", children, ref, ...props }: SelectTriggerProps) { const triggerRef = React.useContext(SelectTriggerContext) const setRef = React.useCallback( (node: HTMLButtonElement | null) => { if (triggerRef) { triggerRef.current = node } if (typeof ref === "function") { return ref(node) } if (ref) { ref.current = node } return undefined }, [triggerRef, ref] ) return ( <SelectPrimitive.Trigger ref={setRef} data-slot="select-trigger" data-size={size} className={mergeClassName(selectTriggerVariants({ size }), className)} {...props} > {children} <SelectPrimitive.Icon data-slot="select-icon" className="flex text-muted-foreground transition-colors group-data-popup-open/select-trigger:text-foreground" > <IconSelector /> </SelectPrimitive.Icon> </SelectPrimitive.Trigger> ) } type SelectContentProps = SelectPrimitive.Popup.Props & Pick< SelectPrimitive.Positioner.Props, "align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger" > function SelectContent({ className, children, side = "bottom", sideOffset = 6, align = "start", alignOffset = 0, alignItemWithTrigger = true, dir, ...props }: SelectContentProps) { const triggerRef = React.useContext(SelectTriggerContext) const setPositioner = React.useCallback( (positioner: HTMLDivElement | null) => { const trigger = triggerRef?.current if (!positioner || dir !== undefined || !trigger?.isConnected) { return } if (getComputedStyle(trigger).direction === "rtl") { positioner.setAttribute("dir", "rtl") } else { positioner.removeAttribute("dir") } }, [triggerRef, dir] ) return ( <SelectPrimitive.Portal> <SelectPrimitive.Positioner ref={setPositioner} dir={dir} data-slot="select-positioner" side={side} sideOffset={sideOffset} align={align} alignOffset={alignOffset} alignItemWithTrigger={alignItemWithTrigger} className="isolate z-50 outline-none select-none focus-visible:outline-hidden" > <SelectPrimitive.Popup data-slot="select-content" className={mergeClassName( "group/select-content relative isolate w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-hidden rounded-lg bg-popover text-popover-foreground shadow-md ring-(length:--hairline) ring-foreground/10 outline-none [--select-item-radius:max(calc(var(--radius-sm)*0.5),calc(var(--radius-lg)-0.25rem))] focus-visible:outline-hidden forced-colors:border", className )} {...props} > <SelectScrollUpButton /> <SelectPrimitive.List data-slot="select-list" className="relative max-h-(--available-height) scroll-py-7 overflow-y-auto overscroll-none p-1 outline-none focus-visible:outline-hidden" > {children} </SelectPrimitive.List> <SelectScrollDownButton /> </SelectPrimitive.Popup> </SelectPrimitive.Positioner> </SelectPrimitive.Portal> ) } function SelectLabel({ className, ...props }: SelectPrimitive.GroupLabel.Props) { return ( <SelectPrimitive.GroupLabel data-slot="select-label" className={mergeClassName( "px-2 pt-2 pb-1 text-xs font-medium text-muted-foreground", className )} {...props} /> ) } function SelectItem({ className, children, ...props }: SelectPrimitive.Item.Props) { return ( <SelectPrimitive.Item data-slot="select-item" className={mergeClassName( "group/select-item relative flex min-h-8 w-full cursor-default items-center gap-2 rounded-(--select-item-radius) py-1.5 ps-2 pe-7 text-start text-sm outline-none select-none focus-visible:outline-hidden data-highlighted:bg-accent data-highlighted:text-accent-foreground forced-colors:data-highlighted:outline-2 forced-colors:data-highlighted:-outline-offset-2 forced-colors:data-highlighted:outline-solid pointer-coarse:min-h-11 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground", className )} {...props} > <SelectPrimitive.ItemText data-slot="select-item-text" className="flex min-w-0 flex-1 items-center gap-2 wrap-anywhere" > {children} </SelectPrimitive.ItemText> <SelectPrimitive.ItemIndicator keepMounted data-slot="select-item-indicator" className="absolute end-2 flex size-4 items-center justify-center opacity-0 transition-[opacity,scale] duration-150 ease-out-quint data-[selected]:opacity-100 motion-safe:scale-50 motion-safe:data-[selected]:scale-100 motion-reduce:transition-none" > <IconCheck className="text-foreground" /> </SelectPrimitive.ItemIndicator> </SelectPrimitive.Item> ) } function SelectSeparator({ className, ...props }: SelectPrimitive.Separator.Props) { return ( <SelectPrimitive.Separator data-slot="select-separator" className={mergeClassName( "pointer-events-none -mx-1 my-1 h-px bg-border", className )} {...props} /> ) } const scrollArrowClassName = "absolute inset-x-0 z-1 flex h-7 cursor-default items-center justify-center text-muted-foreground [&_svg:not([class*='size-'])]:size-4" function SelectScrollUpButton({ className, ...props }: SelectPrimitive.ScrollUpArrow.Props) { return ( <SelectPrimitive.ScrollUpArrow data-slot="select-scroll-up-button" className={mergeClassName( cn( scrollArrowClassName, "top-0 rounded-t-[inherit] bg-linear-to-b from-popover from-55% to-transparent" ), className )} {...props} > <IconChevronUp /> </SelectPrimitive.ScrollUpArrow> ) } function SelectScrollDownButton({ className, ...props }: SelectPrimitive.ScrollDownArrow.Props) { return ( <SelectPrimitive.ScrollDownArrow data-slot="select-scroll-down-button" className={mergeClassName( cn( scrollArrowClassName, "bottom-0 rounded-b-[inherit] bg-linear-to-t from-popover from-55% to-transparent" ), className )} {...props} > <IconChevronDown /> </SelectPrimitive.ScrollDownArrow> ) } export { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, selectTriggerVariants, } export type { SelectContentProps, SelectTriggerProps }lib/motion.ts import * as React from "react" const easeOut = "cubic-bezier(0.23, 1, 0.32, 1)" const easeInOut = "cubic-bezier(0.77, 0, 0.175, 1)" const easeSpring = "linear(0, 0.015 2%, 0.0532 4%, 0.1065 6%, 0.1686 8%, 0.2351 10%, 0.3363 13%, 0.4332 16%, 0.5495 20%, 0.648 24%, 0.7287 28%, 0.807 33%, 0.8646 38%, 0.9128 44%, 0.9447 50%, 0.968 57%, 0.9834 65%, 0.9924 74%, 0.9975 85%, 1)" const duration = { press: 100, release: 200, hover: 150, enter: 200, exit: 150, morph: 300, } as const function prefersReducedMotion() { return ( typeof window === "undefined" || typeof window.matchMedia !== "function" || window.matchMedia("(prefers-reduced-motion: reduce)").matches ) } type SizeAxis = "width" | "height" type SizeMorphOptions = { axis: SizeAxis enabled?: boolean duration?: number easing?: string } function readSize(element: HTMLElement, axis: SizeAxis) { const value = parseFloat(getComputedStyle(element)[axis]) if (Number.isFinite(value)) { return value } const rect = element.getBoundingClientRect() return axis === "width" ? rect.width : rect.height } function attachSizeMorph( element: HTMLElement, axis: SizeAxis, time: number, easing: string ) { if ( typeof MutationObserver === "undefined" || typeof element.animate !== "function" ) { return undefined } let settled = readSize(element, axis) let animation: Animation | null = null const morph = () => { const from = animation ? readSize(element, axis) : settled animation?.cancel() animation = null const to = readSize(element, axis) settled = to if ( Math.abs(from - to) < 0.5 || prefersReducedMotion() || !element.isConnected || element.getClientRects().length === 0 ) { element.removeAttribute("data-morphing") return } element.setAttribute("data-morphing", "") const running = element.animate( [{ [axis]: `${from}px` }, { [axis]: `${to}px` }], { duration: time, easing } ) animation = running running.onfinish = () => { if (animation === running) { animation = null element.removeAttribute("data-morphing") settled = readSize(element, axis) } } } const mutations = new MutationObserver(morph) mutations.observe(element, { childList: true, subtree: true, characterData: true, }) const resize = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => { if (!animation) { settled = readSize(element, axis) } }) resize?.observe(element) return () => { mutations.disconnect() resize?.disconnect() animation?.cancel() element.removeAttribute("data-morphing") } } function useSizeMorph<T extends HTMLElement>({ axis, enabled = true, duration: time = duration.morph, easing = easeOut, }: SizeMorphOptions): React.RefCallback<T> { return React.useCallback( (element: T | null) => { if (!element || !enabled) { return undefined } return attachSizeMorph(element, axis, time, easing) }, [axis, enabled, time, easing] ) } function useSlidingHighlight( barRef: React.RefObject<HTMLElement | null>, highlightRef: React.RefObject<HTMLElement | null>, selector: string, attribute = "data-popup-open" ) { React.useLayoutEffect(() => { const bar = barRef.current const highlight = highlightRef.current if (!bar || !highlight) { return } let current: HTMLElement | null = null const place = (trigger: HTMLElement, instant: boolean) => { if (instant || prefersReducedMotion()) { highlight.setAttribute("data-instant", "") } else { highlight.removeAttribute("data-instant") } const frame = bar.getBoundingClientRect() const box = trigger.getBoundingClientRect() const scale = bar.offsetWidth > 0 ? frame.width / bar.offsetWidth : 1 highlight.style.left = "0px" highlight.style.width = `${box.width / scale}px` highlight.style.height = `${box.height / scale}px` highlight.style.transform = `translate(${(box.left - frame.left) / scale - bar.clientLeft}px, ${(box.top - frame.top) / scale - bar.clientTop}px)` } const sync = () => { const trigger = bar.querySelector<HTMLElement>(selector) if (trigger === current) { if (trigger) { place(trigger, true) } return } const appearing = current === null current = trigger if (!trigger) { highlight.removeAttribute("data-visible") return } place(trigger, appearing) if (appearing) { void highlight.offsetWidth } highlight.setAttribute("data-visible", "") } sync() const mutations = new MutationObserver(sync) mutations.observe(bar, { subtree: true, childList: true, attributes: true, attributeFilter: [attribute], }) const resize = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => { if (current) { place(current, true) } }) resize?.observe(bar) const onScroll = () => { if (current) { place(current, true) } } bar.addEventListener("scroll", onScroll, { capture: true, passive: true }) return () => { mutations.disconnect() resize?.disconnect() bar.removeEventListener("scroll", onScroll, { capture: true }) } }, [barRef, highlightRef, selector, attribute]) } export { duration, easeInOut, easeOut, easeSpring, prefersReducedMotion, useSizeMorph, useSlidingHighlight, } export type { SizeMorphOptions } - 4
Update the import paths to match your project setup.
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"<Select items={fonts} defaultValue="geist">
<SelectTrigger aria-label="Font">
<SelectValue />
</SelectTrigger>
<SelectContent>
{fonts.map((font) => (
<SelectItem key={font.value} value={font.value}>
{font.label}
</SelectItem>
))}
</SelectContent>
</Select>The list opens right over the trigger with the current option lined up on top of the value, so your eye never loses its place. Pass items so SelectValue shows labels instead of raw values. For long lists that need searching, use Combobox.
Select
├── SelectTrigger
│ └── SelectValue
└── SelectContent
├── SelectGroup
│ ├── SelectLabel
│ └── SelectItem
└── SelectSeparatorSizes#
size on SelectTrigger matches Input and Button heights.
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const sizes = ["sm", "default", "lg"] as const
export function SelectSizes() {
return (
<div className="flex flex-wrap items-center gap-3">
{sizes.map((size) => (
<Select key={size} defaultValue="week">
<SelectTrigger size={size} aria-label={`Range (${size})`}>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="day">Today</SelectItem>
<SelectItem value="week">This week</SelectItem>
<SelectItem value="month">This month</SelectItem>
</SelectContent>
</Select>
))}
</div>
)
}Groups and long lists#
Group options with SelectGroup and SelectLabel. Long lists fit the screen and show scroll arrows that scroll when you hover them.
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const zones = [
{
label: "Americas",
items: [
"Los Angeles",
"Denver",
"Chicago",
"New York",
"Toronto",
"Mexico City",
"Bogotá",
"São Paulo",
"Buenos Aires",
],
},
{
label: "Europe",
items: ["London", "Lisbon", "Paris", "Berlin", "Stockholm", "Athens"],
},
{
label: "Asia",
items: ["Dubai", "Mumbai", "Singapore", "Shanghai", "Tokyo", "Seoul"],
},
]
export function SelectGroups() {
return (
<Select defaultValue="Berlin">
<SelectTrigger aria-label="Time zone" className="w-56">
<SelectValue />
</SelectTrigger>
<SelectContent>
{zones.map((zone, index) => (
<SelectGroup key={zone.label}>
{index > 0 ? <SelectSeparator /> : null}
<SelectLabel>{zone.label}</SelectLabel>
{zone.items.map((city) => (
<SelectItem key={city} value={city}>
{city}
</SelectItem>
))}
</SelectGroup>
))}
</SelectContent>
</Select>
)
}With icons#
Put icons in items, and pass a function to SelectValue to show the same icon in the trigger.
"use client"
import {
IconCircleCheck,
IconCircleDashed,
IconCircleHalf2,
IconCircleX,
} from "@tabler/icons-react"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const statuses = [
{ value: "backlog", label: "Backlog", icon: IconCircleDashed },
{ value: "progress", label: "In progress", icon: IconCircleHalf2 },
{ value: "done", label: "Done", icon: IconCircleCheck },
{ value: "canceled", label: "Canceled", icon: IconCircleX },
]
export function SelectIcons() {
return (
<Select defaultValue="progress">
<SelectTrigger aria-label="Status" className="w-44">
<SelectValue>
{(value: string) => {
const status = statuses.find((item) => item.value === value)
if (!status) {
return null
}
const Icon = status.icon
return (
<>
<Icon />
{status.label}
</>
)
}}
</SelectValue>
</SelectTrigger>
<SelectContent>
{statuses.map((status) => (
<SelectItem key={status.value} value={status.value}>
<status.icon />
{status.label}
</SelectItem>
))}
</SelectContent>
</Select>
)
}Multiple#
With multiple, the list stays open while you pick, and the value can summarise long selections.
"use client"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const labels = ["Bug", "Feature", "Design", "Docs", "Performance"]
export function SelectMultiple() {
return (
<Select multiple defaultValue={["Bug", "Design"]}>
<SelectTrigger aria-label="Labels" className="w-56">
<SelectValue placeholder="Add labels">
{(value: string[]) =>
value.length > 2 ? `${value.length} labels` : value.join(", ")
}
</SelectValue>
</SelectTrigger>
<SelectContent alignItemWithTrigger={false}>
{labels.map((label) => (
<SelectItem key={label} value={label}>
{label}
</SelectItem>
))}
</SelectContent>
</Select>
)
}In a form#
Inside Field, the trigger gets its label, description and required validation.
"use client"
import { Form } from "@base-ui/react/form"
import { Button } from "@/components/ui/button"
import {
Field,
FieldDescription,
FieldError,
FieldLabel,
} from "@/components/ui/field"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const roles = [
{ value: "viewer", label: "Viewer" },
{ value: "editor", label: "Editor" },
{ value: "admin", label: "Admin" },
]
export function SelectField() {
return (
<Form
className="flex w-full max-w-xs flex-col items-start gap-4"
onSubmit={(event) => event.preventDefault()}
>
<Field name="role">
<FieldLabel>Role</FieldLabel>
<Select items={roles} required>
<SelectTrigger className="w-full">
<SelectValue placeholder="Choose a role" />
</SelectTrigger>
<SelectContent>
{roles.map((role) => (
<SelectItem key={role.value} value={role.value}>
{role.label}
</SelectItem>
))}
</SelectContent>
</Select>
<FieldDescription>Admins can invite people.</FieldDescription>
<FieldError match="valueMissing">Choose a role to continue.</FieldError>
</Field>
<Button type="submit" size="sm">
Invite
</Button>
</Form>
)
}Disabled and invalid#
Disable the whole select or single options, and mark it invalid with aria-invalid.
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
export function SelectStates() {
return (
<div className="flex flex-wrap items-center gap-3">
<Select defaultValue="pro" disabled>
<SelectTrigger aria-label="Plan" className="w-36">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="pro">Pro</SelectItem>
</SelectContent>
</Select>
<Select defaultValue="weekly">
<SelectTrigger aria-label="Digest" className="w-36">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="daily">Daily</SelectItem>
<SelectItem value="weekly">Weekly</SelectItem>
<SelectItem value="monthly" disabled>
Monthly (soon)
</SelectItem>
</SelectContent>
</Select>
<Select>
<SelectTrigger aria-label="Region" aria-invalid className="w-36">
<SelectValue placeholder="Region" />
</SelectTrigger>
<SelectContent>
<SelectItem value="us">United States</SelectItem>
<SelectItem value="eu">Europe</SelectItem>
</SelectContent>
</Select>
</div>
)
}Below the trigger#
alignItemWithTrigger={false} opens the list under the trigger like a menu. Touch input does this automatically.
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
export function SelectDropdown() {
return (
<Select defaultValue="newest">
<SelectTrigger aria-label="Sort by" className="w-44">
<SelectValue />
</SelectTrigger>
<SelectContent alignItemWithTrigger={false}>
<SelectItem value="newest">Newest first</SelectItem>
<SelectItem value="oldest">Oldest first</SelectItem>
<SelectItem value="popular">Most popular</SelectItem>
</SelectContent>
</Select>
)
}Right to left#
The trigger, list and check follow the reading direction.
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
const cities = [
{ value: "cairo", label: "القاهرة" },
{ value: "riyadh", label: "الرياض" },
{ value: "dubai", label: "دبي" },
]
export function SelectRtl() {
return (
<div dir="rtl">
<Select items={cities} defaultValue="riyadh">
<SelectTrigger aria-label="المدينة" className="w-44">
<SelectValue />
</SelectTrigger>
<SelectContent>
{cities.map((city) => (
<SelectItem key={city.value} value={city.value}>
{city.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)
}| Key | Action |
|---|---|
| SpaceEnter↓↑ | Opens the list from the trigger. |
| ↓↑ | Moves between options. |
| HomeEnd | Moves to the first or last option. |
| A–Z | Jumps to the next option that starts with the typed text. |
| EnterSpace | Picks the highlighted option. |
| Esc | Closes the list and returns focus to the trigger. |
- Label the trigger with
FieldLabeloraria-label. - On touch, the list opens below the trigger instead of over it, so your finger doesn't land on an option.
| Prop | Type | Default |
|---|---|---|
value | Value | Value[] | null | – |
defaultValue | Value | Value[] | null | – |
onValueChange | (value, details) => void | – |
itemsLets SelectValue show labels. | Record<string, ReactNode> | { value, label }[] | – |
multiple | boolean | false |
name | string | – |
required | boolean | false |
disabled | boolean | false |
readOnly | boolean | false |
open | boolean | – |
onOpenChange | (open, details) => void | – |
| Prop | Type | Default |
|---|---|---|
size | "sm" | "default" | "lg" | "default" |
render | ReactElement | (props, state) => ReactElement | <button> |
| Attribute | Description |
|---|---|
data-slot="select-trigger" | The trigger, with data-size. |
data-popup-open | Present while the list is open. |
data-placeholder | Present while nothing is picked. |
| Prop | Type | Default |
|---|---|---|
placeholder | ReactNode | – |
childrenFormat the shown value. | ReactNode | (value) => ReactNode | – |
| Prop | Type | Default |
|---|---|---|
alignItemWithTriggerOpen over the trigger with the current option lined up. | boolean | true |
sideWhen not aligned with the trigger. | "top" | "bottom" | … | "bottom" |
align | "start" | "center" | "end" | "start" |
sideOffset | number | 6 |
| Attribute | Description |
|---|---|
data-slot="select-content" | The popup. |
data-side="none" | Present while aligned over the trigger. |
| Prop | Type | Default |
|---|---|---|
value | Value | – |
disabled | boolean | false |
labelText for typeahead. | string | – |
| Attribute | Description |
|---|---|
data-selected | The picked option. |
data-highlighted | The focused option. |
data-disabled | The option is disabled. |