Select
A picker with an embossed trigger and a floating option list; the highlighted option presses into the surface as you traverse.
@emboss/select
Installation
pnpm dlx shadcn@latest add @emboss/selectUsage
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
export function Example() {
return (
<Select defaultValue="48">
<SelectTrigger aria-label="Sample rate">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="44.1">44.1 kHz</SelectItem>
<SelectItem value="48">48 kHz</SelectItem>
</SelectContent>
</Select>
);
}API reference
Select
The root; renders no element of its own.
| Prop | Type | Default | Description |
|---|---|---|---|
| value / defaultValue / onValueChange | string / string / (value) => void | — | Controlled and uncontrolled selection. |
| open / defaultOpen / onOpenChange | boolean / boolean / (open) => void | — | Controlled and uncontrolled open state. |
SelectTrigger / SelectValue
The embossed control and the rendered current value. The chevron icon is built in.
| Data attribute | Description |
|---|---|
| data-popup-open | Present on the trigger while the list is open. |
SelectContent / SelectItem / SelectGroup / SelectLabel
The floating list and its options. Items show a trailing check when selected.
| Prop | Type | Default | Description |
|---|---|---|---|
| value (SelectItem) | string | — | The option value. |
| Data attribute | Description |
|---|---|
| data-highlighted | Present on the option under the pointer or cursor. |
Keyboard
| Keys | Action |
|---|---|
| EnterSpace | Opens the list; selects the highlighted option. |
| ArrowUpArrowDown | Moves the highlight through the options. |
| Esc | Closes the list. |
Source
View source — select.tsxHide source — select.tsx
"use client";
import { Select as BaseSelect } from "@base-ui/react/select";
import { Check, ChevronDown } from "lucide-react";
import { cn } from "@/lib/utils";
/**
* A picker whose trigger is an embossed control and whose option list
* floats above the page. The highlighted option momentarily presses into
* the surface as you traverse the list.
*
* @example
* <Select defaultValue="48k">
* <SelectTrigger aria-label="Sample rate">
* <SelectValue />
* </SelectTrigger>
* <SelectContent>
* <SelectItem value="44.1k">44.1 kHz</SelectItem>
* <SelectItem value="48k">48 kHz</SelectItem>
* </SelectContent>
* </Select>
*/
function Select(props: React.ComponentProps<typeof BaseSelect.Root>) {
return <BaseSelect.Root {...props} />;
}
function SelectTrigger({
className,
children,
...props
}: React.ComponentProps<typeof BaseSelect.Trigger>) {
return (
<BaseSelect.Trigger
data-slot="select-trigger"
className={cn(
"flex h-(--control-md) w-fit min-w-36 cursor-pointer items-center justify-between gap-2 rounded-md bg-surface-2 px-3 text-sm text-ink shadow-emboss-1 focus-ring transition-[box-shadow] duration-(--duration-release) ease-(--ease-release) select-none hover:shadow-emboss-2 data-disabled:pointer-events-none data-disabled:opacity-50 data-popup-open:shadow-flush",
className,
)}
{...props}
>
{children}
<BaseSelect.Icon className="flex text-ink-faint">
<ChevronDown className="size-4" aria-hidden />
</BaseSelect.Icon>
</BaseSelect.Trigger>
);
}
function SelectValue(props: React.ComponentProps<typeof BaseSelect.Value>) {
return <BaseSelect.Value data-slot="select-value" {...props} />;
}
function SelectContent({
className,
children,
sideOffset = 6,
...props
}: React.ComponentProps<typeof BaseSelect.Popup> & {
sideOffset?: number;
}) {
return (
<BaseSelect.Portal>
<BaseSelect.Positioner sideOffset={sideOffset} className="z-50">
<BaseSelect.Popup
data-slot="select-content"
className={cn(
"max-h-(--available-height) min-w-(--anchor-width) origin-(--transform-origin) overflow-y-auto rounded-md bg-surface-2 p-1 text-ink shadow-float-1 transition-[opacity,scale] duration-(--duration-press) ease-(--ease-press) data-ending-style:scale-95 data-ending-style:opacity-0 data-starting-style:scale-95 data-starting-style:opacity-0",
className,
)}
{...props}
>
{children}
</BaseSelect.Popup>
</BaseSelect.Positioner>
</BaseSelect.Portal>
);
}
function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof BaseSelect.Item>) {
return (
<BaseSelect.Item
data-slot="select-item"
className={cn(
"grid cursor-pointer grid-cols-[1fr_auto] items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-highlighted:bg-well data-highlighted:shadow-deboss-1",
className,
)}
{...props}
>
<BaseSelect.ItemText className="col-start-1">
{children}
</BaseSelect.ItemText>
<BaseSelect.ItemIndicator className="col-start-2 flex">
<Check className="size-3.5" aria-hidden />
</BaseSelect.ItemIndicator>
</BaseSelect.Item>
);
}
function SelectGroup(props: React.ComponentProps<typeof BaseSelect.Group>) {
return <BaseSelect.Group data-slot="select-group" {...props} />;
}
function SelectLabel({
className,
...props
}: React.ComponentProps<typeof BaseSelect.GroupLabel>) {
return (
<BaseSelect.GroupLabel
data-slot="select-label"
className={cn(
"tracking-label px-2 py-1.5 font-mono text-label text-ink-faint uppercase text-engraved",
className,
)}
{...props}
/>
);
}
export {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
SelectGroup,
SelectLabel,
};