tetra-ui Logotetra ui

Select

A component that allows users to select a value from a list of options.

select

Installation

npx shadcn@latest add @tetra-ui/select

Usage

First ensure that you have the PortalHost component in your root layout.

import { PortalHost } from "@/components/ui/portal";

export default function RootLayout() {
  return (
    <>
      <Stack />
      <PortalHost />
    </>
  );
}
import { Button } from "@/components/ui/button";
import {
  Select,
  SelectContentSheet,
  SelectContentSheetBody,
  SelectContentSheetConfirm,
  SelectContentSheetFooter,
  SelectContentSheetHeader,
  SelectContentSheetTitle,
  SelectInput,
  SelectItem,
  SelectItemIndicator,
  SelectItemLabel,
  SelectTrigger,
} from "@/components/ui/select";

const OPTIONS = [
  { label: "Option 1", value: "1" },
  { label: "Option 2", value: "2" },
  { label: "Option 3", value: "3" },
];

<Select options={OPTIONS} placeholder="Select...">
  <SelectTrigger asChild>
    <SelectInput />
  </SelectTrigger>
  <SelectContentSheet>
    <SelectContentSheetHeader>
      <SelectContentSheetTitle>Select a value</SelectContentSheetTitle>
    </SelectContentSheetHeader>
    <SelectContentSheetBody>
      {OPTIONS.map((option) => (
        <SelectItem
          key={option.value}
          label={option.label}
          value={option.value}
        />
      ))}
    </SelectContentSheetBody>
    <SelectContentSheetFooter>
      <SelectContentSheetConfirm asChild>
        <Button>Confirm</Button>
      </SelectContentSheetConfirm>
    </SelectContentSheetFooter>
  </SelectContentSheet>
</Select>

Controlled

Pass value and onValueChange for controlled selection:

const [value, setValue] = useState<string>();

<Select
  options={OPTIONS}
  value={value}
  onValueChange={setValue}
  placeholder="Select..."
>
  {/* ... */}
</Select>

Invalid

Pass invalid to show a destructive border on the trigger input:

<Select options={OPTIONS} invalid placeholder="Select...">
  {/* ... */}
</Select>

Popover content

For shorter option lists, use SelectContentPopover instead of a sheet:

<Select options={OPTIONS} placeholder="Select...">
  <SelectTrigger asChild>
    <SelectInput />
  </SelectTrigger>
  <SelectContentPopover>
    {OPTIONS.map((option) => (
      <SelectItem
        key={option.value}
        label={option.label}
        value={option.value}
      />
    ))}
  </SelectContentPopover>
</Select>

When using popover content, PortalHost must be present in your root layout.

Changelog

30-05-2026 - Migrate to BottomSheet

Use the new BottomSheet component for the sheet content instead of the NativeSheet component.

On this page