Select
A form-ready dropdown select component supporting controlled and uncontrolled usage with four visual variants.
import { Select } from "@tenstorrent/vesper/select";
export default function SelectDemo() {
return (
<Select
aria-label="Fruit"
placeholder="Select a fruit"
options={[
{ value: "apple", label: "Apple" },
{ value: "banana", label: "Banana" },
{ value: "cherry", label: "Cherry" },
]}
/>
);
}Select an accessible name by either pairing it with a <label> via htmlFor or giving it an aria-label. Without one, assistive technology announces the field with no indication of what it is for.Options
| Prop | Type | Description | Default |
|---|---|---|---|
options | (SelectItem | string)[] | The list of selectable options displayed in the dropdown. Strings are treated as both the label and the value. | — |
size | "sm" | "md" | "lg" | The size of the select trigger. Affects height, padding, and typography. | "md" |
variant | "default" | "warning" | "success" | "error" | The visual variant, which determines color scheme. | "default" |
placeholder | string | Placeholder text shown in the trigger when no value is selected. | "Select an option" |
icon | ReactNode | An optional icon rendered at the leading edge of the trigger. | — |
value | string | null | The currently selected value (controlled mode). Pass null to represent no selection. | — |
defaultValue | string | null | The initial selected value (uncontrolled mode). | — |
onValueChange | (value: string | null) => void | Callback invoked with the new value whenever the selection changes, or null when it is cleared. | — |
name | string | The form field name submitted with form data. | — |
form | string | Associates the select with a <form> element by its id. | — |
id | string | An identifier applied to the trigger, used to reference it from form validation errors. | — |
required | boolean | When true, marks the select as required for native form validation. | false |
disabled | boolean | When true, prevents interaction with the trigger and dropdown. | false |
container | HTMLElement | ShadowRoot | null | RefObject<HTMLElement | ShadowRoot | null> | Specify the element or shadow root to portal the dropdown into. | — |
ref | Ref<HTMLButtonElement> | A ref forwarded to the underlying select trigger <button> element. | — |
Form props (value, disabled, name, form, etc.) are forwarded to the hidden <input> element that holds the selected value. All other props are forwarded to the underlying <button> element.
SelectItem options
| Property | Type | Description |
|---|---|---|
label | string | The text displayed for this option in the dropdown, and in the trigger once it is selected. |
value | string | The underlying value submitted with form data and passed to onValueChange. Must be unique. |
A plain string option is shorthand for a SelectItem whose label and value are both that string.
If your list is long enough that users will want to search it, reach for the Combobox component instead: it renders the same trigger sizes and variants, but filters its options as the user types.
Examples
Defining options
Each entry in the options array can either be a SelectItem, or a plain string. Use SelectItem objects when the value associated with each item differs from the text you want to display:
import { Select } from "@tenstorrent/vesper/select";
export default function ObjectOptionsSelect() {
return (
<Select
aria-label="Region"
placeholder="Select a region"
options={[
{ label: "United States", value: "us" },
{ label: "Europe", value: "eu" },
{ label: "Asia Pacific", value: "ap" },
]}
/>
);
}When the displayed text is the same as the value of the item, a string option is a convenient shorthand:
import { Select } from "@tenstorrent/vesper/select";
export default function StringOptionsSelect() {
return (
<Select
aria-label="Country"
placeholder="Select a country"
options={["Canada", "Japan", "Norway"]}
/>
);
}Both shapes can be mixed in the same options array. Options are rendered in the order they are provided, and the selected one is marked with a checkmark when the dropdown is open.
Uncontrolled vs controlled
Render a Select in an uncontrolled fashion to let it keep track of its own selection. Pass defaultValue if you need an option to be selected initially:
import { Select } from "@tenstorrent/vesper/select";
export default function UncontrolledSelect() {
return (
<Select
aria-label="Region"
placeholder="Select a region"
defaultValue="eu"
options={[
{ label: "United States", value: "us" },
{ label: "Europe", value: "eu" },
{ label: "Asia Pacific", value: "ap" },
]}
/>
);
}If you need to control which option is selected, use the value and onValueChange props. value is the value of the selected option, and is null when nothing is selected. There is no built-in way for a user to clear a selection, so pass null when you need to return the select to its placeholder state:
Selected value: apple
import { useState } from "react";
import { Button } from "@tenstorrent/vesper/button";
import { Select } from "@tenstorrent/vesper/select";
import { Typography } from "@tenstorrent/vesper/typography";
const FRUITS = [
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
{ label: "Cherry", value: "cherry" },
{ label: "Mango", value: "mango" },
{ label: "Peach", value: "peach" },
];
export default function ControlledSelect() {
const [fruit, setFruit] = useState<string | null>("apple");
return (
<div
style={{
display: "flex",
flexDirection: "column",
gap: "var(--vesper-spacing-4)",
}}
>
<Select
aria-label="Fruit"
placeholder="Select a fruit"
options={FRUITS}
value={fruit}
onValueChange={setFruit}
/>
<Typography variant="copy-sm">
Selected value: {fruit ?? "null"}
</Typography>
<Button size="sm" onClick={() => setFruit(null)}>
Reset
</Button>
</div>
);
}Different sizes
The Select component can be rendered at sm, md, or lg size, defaulting to md. Size affects the height, padding, and text styles of the trigger, as well as the size of its icons.
import { Select } from "@tenstorrent/vesper/select";
const COUNTRIES = [
{ label: "Canada", value: "ca" },
{ label: "Japan", value: "jp" },
{ label: "Norway", value: "no" },
];
export default function SelectSizes() {
return (
<div
style={{
display: "flex",
flexDirection: "column",
gap: "var(--vesper-spacing-4)",
}}
>
<Select
size="sm"
aria-label="Country"
placeholder="A small select"
options={COUNTRIES}
/>
<Select
size="md"
aria-label="Country"
placeholder="A medium select"
options={COUNTRIES}
/>
<Select
size="lg"
aria-label="Country"
placeholder="A large select"
options={COUNTRIES}
/>
</div>
);
}Rendering an icon
You can render an icon at the leading edge of the trigger via the icon prop. The icon is decorative, and stays in place as the selection changes:
import { Globe } from "@tenstorrent/vesper/icons";
import { Select } from "@tenstorrent/vesper/select";
export default function SelectWithIcon() {
return (
<Select
aria-label="Region"
icon={<Globe />}
placeholder="Select a region"
options={[
{ label: "United States", value: "us" },
{ label: "Europe", value: "eu" },
{ label: "Asia Pacific", value: "ap" },
]}
/>
);
}Disabling the select
Pass disabled to prevent all interaction with the trigger and its dropdown. A disabled Select still displays its current selection, but is skipped in the tab order and excluded from form data:
import { Select } from "@tenstorrent/vesper/select";
export default function DisabledSelect() {
return (
<Select
disabled
aria-label="Region"
placeholder="Select a region"
defaultValue="eu"
options={[
{ label: "United States", value: "us" },
{ label: "Europe", value: "eu" },
{ label: "Asia Pacific", value: "ap" },
]}
/>
);
}Usage in forms
Select renders a hidden form control alongside its trigger, so passing a name includes the selected value in form submissions, and required participates in native form validation. Use the form prop to associate the field with a <form> rendered elsewhere on the page.
import { useState } from "react";
import { Button } from "@tenstorrent/vesper/button";
import { Select } from "@tenstorrent/vesper/select";
import { Typography } from "@tenstorrent/vesper/typography";
const FRUITS = [
{ label: "Apple", value: "apple" },
{ label: "Banana", value: "banana" },
{ label: "Cherry", value: "cherry" },
{ label: "Mango", value: "mango" },
{ label: "Peach", value: "peach" },
];
export default function FormSelectDemo() {
const [submitted, setSubmitted] = useState<string | null>(null);
return (
<form
className="gap-vesper-4 flex flex-col"
onSubmit={(event) => {
event.preventDefault();
const data = new FormData(event.currentTarget);
setSubmitted(String(data.get("fruit") ?? ""));
}}
>
<Select
required
name="fruit"
aria-label="Fruit"
placeholder="Select a fruit"
options={FRUITS}
/>
<Button size="sm" type="submit">
Submit
</Button>
{submitted !== null && (
<Typography variant="copy-sm">Submitted value: {submitted}</Typography>
)}
</form>
);
}The value submitted is the value of the selected option, not the label displayed in the trigger. The form above submits fruit=apple when "Apple" is selected.
Accessing the underlying element
Use the ref prop when you need direct access to the underlying trigger <button> element, eg. to focus it when a page loads:
import { useEffect, useRef } from "react";
import { Select } from "@tenstorrent/vesper/select";
export default function RegionSelect() {
const ref = useRef<HTMLButtonElement>(null);
useEffect(() => {
ref.current?.focus();
}, []);
return (
<Select
aria-label="Region"
placeholder="Select a region"
options={["United States", "Europe", "Asia Pacific"]}
ref={ref}
/>
);
}