Select
Picks one value, or several, from a short fixed list.
import { Select } from "@openperf/deft";
import { Preview } from "../../src/Preview";
import { OPTIONS } from "./data";
export default function SelectBasic() {
return (
<Preview overlay={true}>
<Select.Root defaultValue="option-1" items={OPTIONS}>
<Select.Trigger />
<Select.Content>
{OPTIONS.map((option) => (
<Select.Item key={option.value} value={option.value}>
{option.label}
</Select.Item>
))}
</Select.Content>
</Select.Root>
</Preview>
);
}
- After selection, the trigger shows the current value. Before selection, use a specific
placeholder, not a generic label like “Select…”. - Use Combobox for lists longer than ten items or lists that people need to search.
- Pass
multipletoSelect.Rootfor several values; a check marks the chosen ones. - The trigger takes the variants and sizes of Button. Inside a List, use
variant="field". - The list opens below the trigger, at least as wide as it, and scrolls when it runs out of room.
Inline
import { Select } from "@openperf/deft";
import { Preview } from "../../src/Preview";
import { OPTIONS } from "./data";
export default function SelectInline() {
return (
<Preview overlay={true}>
<p>
Text with{" "}
<Select.Root defaultValue="option-1" items={OPTIONS}>
<Select.Trigger size="inline" variant="ghost" />
<Select.Content>
{OPTIONS.map((option) => (
<Select.Item key={option.value} value={option.value}>
{option.label}
</Select.Item>
))}
</Select.Content>
</Select.Root>{" "}
in a sentence.
</p>
</Preview>
);
}
size="inline" with variant="ghost" sits inside a line of text and follows its size.
With pictures
import { Select } from "@openperf/deft";
import { useState } from "react";
import { Preview } from "../../src/Preview";
const OPTIONS = [
{ value: "accent", label: "Accent", color: "var(--accent-emphasis)" },
{ value: "warning", label: "Warning", color: "var(--chart-warning)" },
{ value: "danger", label: "Danger", color: "var(--chart-danger)" },
];
export default function SelectMedia() {
const [value, setValue] = useState<null | string>("accent");
return (
<Preview overlay={true}>
<Select.Root items={OPTIONS} onValueChange={setValue} value={value}>
<Select.Trigger
media={
<span
className="size-3.5 rounded-xs"
style={{ background: OPTIONS.find((option) => option.value === value)?.color }}
/>
}
/>
<Select.Content>
{OPTIONS.map((option) => (
<Select.Item
key={option.value}
media={<span className="size-3.5 rounded-xs" style={{ background: option.color }} />}
value={option.value}
>
{option.label}
</Select.Item>
))}
</Select.Content>
</Select.Root>
</Preview>
);
}
media on Select.Item puts a swatch or a thumbnail before the label. Pass the chosen item’s picture to Select.Trigger with the same media prop so the trigger shows it too.
API
variant?"default" | "field" | "primary" | "ghost" | "danger"
On Trigger.
"default" | "field" | "primary" | "ghost" | "danger""default"size?"inline" | "xs" | "sm" | "md"
On Trigger.
"inline" | "xs" | "sm" | "md""md"placeholder?ReactNode
On Trigger. Shown before a value is picked.
ReactNodechildren?ReactNode
On Trigger. Shown before the value, such as an icon or a label.
ReactNodemedia?ReactNode
On Trigger and Item. A picture before the label, such as a swatch or a thumbnail.
ReactNodealign?"start" | "center" | "end"
On Content.
"start" | "center" | "end""start"icon?TablerIcon
On Item.
TablerIconParts: all parts of Base UI Select, such as Root, Group and Value, with Deft’s Content, GroupLabel, Item, Separator and Trigger in place of the Base UI ones. Content renders the portal, positioner, popup and list.