Skip to main content

Select

Select is a form control for choosing options from a list, like picking a project status or assigning skills. It opens a menu of options and can include a search input for long lists.

Import

import { Select } from '@vortexlabs/vortex';
import type { SelectProps } from '@vortexlabs/vortex';

Other links

<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.

Choose the current stage of the project.
"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>
  );
};

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.

Choose all the skills needed for this project.
"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.

Managed by your workspace.
"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

Key
Description
Enter
Open the menu. While it's open, select the highlighted option.
Space
On Select.Field, same as Enter. In a combobox, Space types into the search.
Down arrowUp arrow
Move the highlight through options while the menu is open. On a closed combobox, open the menu.
Escape
Close the menu.
Tab
Close the menu and move focus to the next element.
Include Select.Label to name the field. Helper and error text are linked to it automatically.