<Select.Root
width="280px"
size="sm"
variant="outline"
>
<Select.Label>Project status</Select.Label>
<Select.Field value="Active" placeholder="Choose a status">
<Select.Item>Planned</Select.Item>
<Select.Item selected>Active</Select.Item>
<Select.Item>Done</Select.Item>
</Select.Field>
<Select.ErrorMessage>Choose a status to continue.</Select.ErrorMessage>
</Select.Root>Features
- Selects one option or several.
- Searches long lists.
- Loads options from items or an async source.
- Shows helper text, errors, and loading states.
Usage
Place Select.Label and Select.Field inside Select.Root, and pass options with loadOptions. Keep the selected option ID in state with value and onValueChange. Define loadOptions outside the component or memoize it; a new function reloads the options.
"use client";
import { Select } from "@vortexlabs/vortex";
import type { SelectLoadOptions } from "@vortexlabs/vortex";
import { useState } from "react";
const loadStatuses: SelectLoadOptions = () => [
{ value: "planned", label: "Planned" },
{ value: "active", label: "Active" },
{ value: "done", label: "Done" },
];
export const SelectLoadedExample = () => {
const [value, setValue] = useState("planned");
return (
<Select.Root width="100%">
<Select.Label>Project status</Select.Label>
<Select.Field
value={value}
onValueChange={(next) => setValue(next ?? "")}
loadOptions={loadStatuses}
placeholder="Choose a status"
/>
<Select.HelperText>Choose the current stage of the project.</Select.HelperText>
</Select.Root>
);
};Static Items
Use Select.Item children for sections or custom rows. Mark the current item selected, update state in onClick, and pass its text as value.
"use client";
import { Select } from "@vortexlabs/vortex";
import { useState } from "react";
export const SelectStaticExample = () => {
const [value, setValue] = useState("Design");
return (
<Select.Root width="100%">
<Select.Label>Team</Select.Label>
<Select.Field value={value} placeholder="Choose a team">
<Select.SectionLabel>Product</Select.SectionLabel>
<Select.Item selected={value === "Design"} onClick={() => setValue("Design")}>
Design
</Select.Item>
<Select.Item
selected={value === "Engineering"}
onClick={() => setValue("Engineering")}
>
Engineering
</Select.Item>
<Select.SectionLabel>Operations</Select.SectionLabel>
<Select.Item selected={value === "Support"} onClick={() => setValue("Support")}>
Support
</Select.Item>
</Select.Field>
</Select.Root>
);
};Search
Select.Combobox adds a search input and passes the query to loadOptions, which can return a promise. Set selection="single" to pick one option.
"use client";
import { Select } from "@vortexlabs/vortex";
import type { SelectLoadOptions } from "@vortexlabs/vortex";
import { useState } from "react";
const loadProjects: SelectLoadOptions = async (query) => {
await new Promise((resolve) => setTimeout(resolve, 600));
return [
{ value: "website", label: "Website redesign" },
{ value: "mobile", label: "Mobile app" },
{ value: "system", label: "Design system" },
].filter((option) => option.label.toLowerCase().includes(query.toLowerCase()));
};
export const SelectAsyncExample = () => {
const [project, setProject] = useState<string | null>(null);
return (
<Select.Root width="100%">
<Select.Label>Project</Select.Label>
<Select.Combobox
selection="single"
loadOptions={loadProjects}
selectedValue={project}
onSelectedValueChange={setProject}
placeholder="Search projects"
loadingLabel="Loading projects"
emptyState="No projects match your search"
/>
</Select.Root>
);
};Multiple Selection
Combobox defaults to multiple selection, shown as removable chips. onSelectedValuesChange receives the updated IDs, including after a chip is removed.
design
"use client";
import { Select } from "@vortexlabs/vortex";
import type { SelectLoadOptions } from "@vortexlabs/vortex";
import { useState } from "react";
const loadSkills: SelectLoadOptions = (query) =>
[
{ value: "design", label: "Design" },
{ value: "engineering", label: "Engineering" },
{ value: "research", label: "Research" },
].filter((option) => option.label.toLowerCase().includes(query.toLowerCase()));
export const SelectMultipleExample = () => {
const [skills, setSkills] = useState<string[]>(["design"]);
return (
<Select.Root width="100%">
<Select.Label>Skills</Select.Label>
<Select.Combobox
loadOptions={loadSkills}
selectedValues={skills}
onSelectedValuesChange={setSkills}
placeholder="Add skills"
emptyState="No matching skills"
/>
<Select.HelperText>
Choose all the skills needed for this project.
</Select.HelperText>
</Select.Root>
);
};Sizes and Variants
The defaults are sm and outline.
"use client";
import { Select, Stack } from "@vortexlabs/vortex";
import type { SelectLoadOptions } from "@vortexlabs/vortex";
import { useState } from "react";
const loadPriorities: SelectLoadOptions = () => [
{ value: "normal", label: "Normal" },
{ value: "high", label: "High" },
];
export const SelectAppearanceExample = () => {
const [value, setValue] = useState("normal");
return (
<Stack gap={4} width="100%">
<Select.Root size="sm" variant="outline">
<Select.Label>Small outline</Select.Label>
<Select.Field
loadOptions={loadPriorities}
value={value}
onValueChange={(next) => setValue(next ?? "")}
/>
</Select.Root>
<Select.Root size="md" variant="outline">
<Select.Label>Medium outline</Select.Label>
<Select.Field
loadOptions={loadPriorities}
value={value}
onValueChange={(next) => setValue(next ?? "")}
/>
</Select.Root>
<Select.Root size="sm" variant="fill">
<Select.Label>Small fill</Select.Label>
<Select.Field
loadOptions={loadPriorities}
value={value}
onValueChange={(next) => setValue(next ?? "")}
/>
</Select.Root>
</Stack>
);
};Disabled and Error States
Select.ErrorMessage shows only while error is true; this example sets it after the menu closes without a choice. Select.HelperText always shows.
"use client";
import { Select, Stack } from "@vortexlabs/vortex";
import type { SelectLoadOptions } from "@vortexlabs/vortex";
import { useState } from "react";
const loadRegions: SelectLoadOptions = () => [
{ value: "au", label: "Australia" },
{ value: "eu", label: "Europe" },
];
export const SelectStatesExample = () => {
const [region, setRegion] = useState("");
const [touched, setTouched] = useState(false);
return (
<Stack gap={4} width="100%">
<Select.Root disabled>
<Select.Label>Managed region</Select.Label>
<Select.Field loadOptions={loadRegions} value="au" />
<Select.HelperText>Managed by your workspace.</Select.HelperText>
</Select.Root>
<Select.Root error={touched && !region}>
<Select.Label>Storage region</Select.Label>
<Select.Field
loadOptions={loadRegions}
value={region}
onValueChange={(next) => setRegion(next ?? "")}
onClose={() => setTouched(true)}
placeholder="Choose a region"
/>
<Select.ErrorMessage>Choose a region before continuing.</Select.ErrorMessage>
</Select.Root>
</Stack>
);
};Accessibility
Select.Field, same as Enter. In a combobox, Space types into the search.Select.Label to name the field. Helper and error text are linked to it automatically.