Skip to main content

Select

Select is a form control that lets people choose options from a list, such as picking a project status or assigning skills.

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.

Anatomy

<Select.Root>
  <Select.Label>Project status</Select.Label>
  <Select.Field placeholder="Choose a status">
    <Select.SectionLabel>Open</Select.SectionLabel>
    <Select.Item>Active</Select.Item>
    <Select.Item>Done</Select.Item>
  </Select.Field>
  <Select.HelperText>Choose the current stage of the project.</Select.HelperText>
  <Select.ErrorMessage>Choose a status.</Select.ErrorMessage>
</Select.Root>
Component Part
Description
Shares size, variant, and disabled or error state with the field.
Names the field.
Opens the option menu. Use Select.Combobox instead to add search.
Names a group of options.
Shows one option.
Adds supporting text below the field.
Explains the error while error is set.

Loaded Options

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 React from "react";

const loadStatuses: SelectLoadOptions = () => [
  { value: "planned", label: "Planned" },
  { value: "active", label: "Active" },
  { value: "done", label: "Done" },
];

export const SelectLoadedExample = () => {
  const [value, setValue] = React.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 React from "react";

export const SelectStaticExample = () => {
  const [value, setValue] = React.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 React 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] = React.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

Choose all the skills needed for this project.
"use client";

import { Select } from "@vortexlabs/vortex";
import type { SelectLoadOptions } from "@vortexlabs/vortex";
import React 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] = React.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

Set size on Select.Root to size the trigger, label, menu items, and Combobox badges together.

Set size on Select.Item to override that item's text and selection indicator size. Combobox badges still follow Select.Root.size.

"use client";

import { Select, Stack } from "@vortexlabs/vortex";
import type { SelectLoadOptions } from "@vortexlabs/vortex";
import React from "react";

const loadPriorities: SelectLoadOptions = () => [
  { value: "normal", label: "Normal" },
  { value: "high", label: "High" },
];

export const SelectAppearanceExample = () => {
  const [value, setValue] = React.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 React from "react";

const loadRegions: SelectLoadOptions = () => [
  { value: "au", label: "Australia" },
  { value: "eu", label: "Europe" },
];

export const SelectStatesExample = () => {
  const [region, setRegion] = React.useState("");
  const [touched, setTouched] = React.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
Tab
Close the menu and move focus to the next element.
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
Open the menu when the field or combobox is closed. Otherwise, move the highlight through options.
Escape
Close the menu.
Include Select.Label to name the field. Helper and error text are linked to it automatically.