Select
PortedPick one value (or several with multiple) from a list. readOnly keeps the value but still opens the list to show the options. Native <button> attributes and ref pass through to the trigger.
import { Select } from "@etched/monet/select";
const sites = [
{ value: "tpe-2", label: "TPE-2" },
{ value: "sjc-1", label: "SJC-1" },
{ value: "aus-3", label: "AUS-3 (offline)", disabled: true },
];
export function Preview() {
return <Select aria-label="Site" items={sites} placeholder="Choose a site" />;
}Installation
import { Select, SelectGroup, SelectGroupLabel, SelectOption, SelectSeparator } from "@etched/monet/select";Examples
Basics
import { Select } from "@etched/monet/select";
const statuses = { queued: "Queued", running: "Running", passed: "Passed", failed: "Failed" };
const sites = [
{ value: "tpe-2", label: "TPE-2" },
{ value: "sjc-1", label: "SJC-1" },
{ value: "aus-3", label: "AUS-3 (offline)", disabled: true },
];
export function Basics() {
return (
<div className="row">
<Select aria-label="Status" items={statuses} defaultValue="running" />
<Select aria-label="Site" items={sites} placeholder="Choose a site" />
<Select aria-label="Status, small" size="sm" items={statuses} defaultValue="passed" />
<Select aria-label="Status, disabled" items={statuses} defaultValue="queued" disabled />
</div>
);
}Pill (status in a table)
The trigger takes the color of the selected option's tone or hue.
import { Select } from "@etched/monet/select";
const statuses = { queued: "Queued", running: "Running", passed: "Passed", failed: "Failed" };
const stages = [
{ value: "todo", label: "To Do", tone: "neutral" },
{ value: "scheduling", label: "Scheduling", tone: "orange" },
{ value: "scheduled", label: "Scheduled", tone: "purple" },
{ value: "touch", label: "In Touch", tone: "teal" },
{ value: "interviewing", label: "Interviewing", tone: "cyan" },
{ value: "references", label: "References check", tone: "cyan" },
{ value: "hired", label: "Hired", tone: "green" },
{ value: "passed", label: "We Passed", tone: "red" },
{ value: "trial", label: "Work trial (custom)", hue: 236 },
] as const;
export function PillStatusInATable() {
return (
<div className="row">
<Select aria-label="Stage" appearance="pill" items={stages} defaultValue="interviewing" />
<Select aria-label="Stage, hired" appearance="pill" items={stages} defaultValue="hired" />
<Select aria-label="Stage, custom" appearance="pill" items={stages} defaultValue="trial" />
<Select aria-label="Status, pill" appearance="pill" items={statuses} defaultValue="running" />
</div>
);
}In a form
Status
Runs in Failed state are retried at 02:00.
Site (optional)
Owner
Choose an owner before you save.
import { Select } from "@etched/monet/select";
const statuses = { queued: "Queued", running: "Running", passed: "Passed", failed: "Failed" };
const sites = [
{ value: "tpe-2", label: "TPE-2" },
{ value: "sjc-1", label: "SJC-1" },
{ value: "aus-3", label: "AUS-3 (offline)", disabled: true },
];
export function InAForm() {
return (
<div className="grid">
<Select label="Status" items={statuses} defaultValue="running" description="Runs in Failed state are retried at 02:00." />
<Select label="Site" items={sites} placeholder="Choose a site" required={false} />
<Select label="Owner" items={{ ana: "Ana Ruiz", sam: "Sam Lee" }} placeholder="Choose an owner" error="Choose an owner before you save." />
</div>
);
}Descriptions
Priority
import { Select } from "@etched/monet/select";
const priorities = [
{ value: "p0", label: "P0", description: "Drop everything and page the owner." },
{ value: "p1", label: "P1", description: "Fix in this sprint." },
{ value: "p2", label: "P2", description: "Fix when there is time." },
];
export function Descriptions() {
return (
<div className="grid">
<Select label="Priority" items={priorities} defaultValue="p1" />
</div>
);
}Loading and read-only
loading shows a pulsing line and disables the trigger while the options come from the server. readOnly keeps the value; the list still opens to show the options.
Approver
Status
Set by the bench. You cannot change it here.
import { Select } from "@etched/monet/select";
const statuses = { queued: "Queued", running: "Running", passed: "Passed", failed: "Failed" };
export function LoadingAndReadOnly() {
return (
<div className="grid">
<Select label="Approver" loading items={[]} placeholder="Choose an approver" />
<Select label="Status" readOnly items={statuses} defaultValue="running" description="Set by the bench. You cannot change it here." />
</div>
);
}Groups and multiple
import { Select, SelectGroup, SelectGroupLabel, SelectOption, SelectSeparator } from "@etched/monet/select";
const statuses = { queued: "Queued", running: "Running", passed: "Passed", failed: "Failed" };
export function GroupsAndMultiple() {
return (
<div className="row">
<Select aria-label="Bench" placeholder="Choose a bench">
<SelectGroup>
<SelectGroupLabel>Thermal</SelectGroupLabel>
<SelectOption value="b-14">B-14</SelectOption>
<SelectOption value="b-15">B-15</SelectOption>
</SelectGroup>
<SelectSeparator />
<SelectGroup>
<SelectGroupLabel>Power</SelectGroupLabel>
<SelectOption value="p-02">P-02</SelectOption>
</SelectGroup>
</Select>
<Select
aria-label="Statuses"
multiple
items={statuses}
defaultValue={["failed", "running"]}
placeholder="Any status"
// SAFETY: the items are the statuses record, so each value is one of its keys.
renderValue={(values) => (values.length === 1 ? statuses[values[0] as keyof typeof statuses] : `${values.length} statuses`)}
/>
</div>
);
}Accessibility
Enter,SpaceorArrowDownopens the list. In it, arrows andHome/Endmove, letters jump to an option,Enterpicks,Escapecloses.- The trigger is a
comboboxwitharia-haspopup="listbox"andaria-expanded, named bylabeloraria-label. Options areoptions witharia-selected. descriptionanderrordescribe the trigger, anderrorsetsaria-invalid.loadingmakes it disabled andaria-busy;readOnlysetsaria-readonly.- Give it a
label, or anaria-labelwhere no label shows, such as in a table cell.
API reference
SelectGroupLabel
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | – |
SelectGroup
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | – |
SelectOption
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | – | |
| level | 1 | 2 | 3 | 4 | – | A step on the green ramp, for an ordinal score such as conviction 1–4. Wins over hue and tone. A Pill with a level is a small score box, and a list option with one takes the fill across its row. |
| value * | T | – | |
| children * | ReactNode | – | |
| description | ReactNode | – | Second line under the label. |
Select
| Prop | Type | Default | Description |
|---|---|---|---|
| label | ReactNode | – | Visible label. Without one, give the control an aria-label. |
| labelTooltip | ReactNode | – | More about the field, behind an info button after the label. |
| labelTooltipLabel | string | "More information" | Names the info button. |
| description | ReactNode | – | Help text below the control. Hidden while an error shows. |
| error | FieldError | – | |
| items | ReadonlyArray<SelectItem<T>> | ([T] extends [string] ? Record<string, ReactNode> : never) | – | Options as { label, value } objects; for string values, also a { value: label } record. Or pass SelectOption children. |
| children | ReactNode | – | |
| size | "sm" | "base" | "base" | Same heights as Input. |
| appearance | "default" | "pill" | "ghost" | "box" | "default" | pill is a rounded, borderless trigger for statuses in tables (Orbit\'s status dropdown). ghost has no edge or fill until hover, for values in table cells; it takes the selected option\'s ink. |
| placeholder | ReactNode | – | Shown in the trigger while nothing is selected. |
| renderValue | (value: Multiple extends true ? T[] : T) => ReactNode | – | Custom trigger text for the selected value (or values when multiple). |
| className | string | – | Class on the trigger, for layout such as width. |
| side | BaseSelect.Positioner.Props["side"] | "bottom" | Which side of the trigger the list opens on. |
| align | BaseSelect.Positioner.Props["align"] | "start" | |
| alignItemWithTrigger | boolean | false | Overlay the list on the trigger like a native select. |
| loading | boolean | – | While the options load from the server: a pulsing line in place of the value, and the trigger is disabled and busy (Kumo). |
| container | BaseSelect.Portal.Props["container"] | document.body | The element the list's portal goes into, for example a shadow root. |
SelectSeparator
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | – |