import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from "@registry/components/ui/select";

export function SelectExample() {
  const items = [
    { value: "apple", label: "Apple" },
    { value: "banana", label: "Banana" },
    { value: "blueberry", label: "Blueberry" },
    { value: "grapes", label: "Grapes" },
    { value: "pineapple", label: "Pineapple" },
  ];

  return (
    <div className="flex flex-wrap items-center gap-4">
      <Select items={items}>
        <SelectTrigger className="w-44">
          <SelectValue placeholder="Select a fruit" />
        </SelectTrigger>
        <SelectContent>
          {items.map((item) => (
            <SelectItem key={item.value} value={item.value}>
              {item.label}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  );
}

Examples

Trigger variants

The trigger supports the same variant and size props as Button.

import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from "@registry/components/ui/select";

export function SelectVariantsExample() {
  const variants = [
    { value: "outline", label: "Outline" },
    { value: "solid", label: "Solid" },
  ];

  const sizes = [
    { value: "sm", label: "Small" },
    { value: "default", label: "Default" },
    { value: "lg", label: "Large" },
  ];

  return (
    <div className="flex flex-wrap items-center gap-4">
      <Select items={variants} defaultValue="outline">
        <SelectTrigger variant="outline" className="w-44">
          <SelectValue placeholder="Variant" />
        </SelectTrigger>
        <SelectContent>
          {variants.map((item) => (
            <SelectItem key={item.value} value={item.value}>
              {item.label}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>

      <Select items={sizes} defaultValue="sm">
        <SelectTrigger variant="outline" size="sm" className="w-44">
          <SelectValue placeholder="Size" />
        </SelectTrigger>
        <SelectContent>
          {sizes.map((item) => (
            <SelectItem key={item.value} value={item.value}>
              {item.label}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  );
}

With separator

Use SelectSeparator to visually group related options.

import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue, SelectSeparator } from "@registry/components/ui/select";

export function SelectWithGroupsExample() {
  const items = [
    { value: "react", label: "React" },
    { value: "vue", label: "Vue" },
    { value: "svelte", label: "Svelte" },
    { value: "next", label: "Next.js" },
    { value: "nuxt", label: "Nuxt" },
    { value: "astro", label: "Astro" },
  ];

  return (
    <div className="flex flex-wrap items-center gap-4">
      <Select items={items} defaultValue="react">
        <SelectTrigger variant="outline" className="w-56">
          <SelectValue placeholder="Select a framework" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="react">React</SelectItem>
          <SelectItem value="vue">Vue</SelectItem>
          <SelectItem value="svelte">Svelte</SelectItem>
          <SelectSeparator />
          <SelectItem value="next">Next.js</SelectItem>
          <SelectItem value="nuxt">Nuxt</SelectItem>
          <SelectItem value="astro">Astro</SelectItem>
        </SelectContent>
      </Select>
    </div>
  );
}

Controlled

Manage the selected value with React state.

import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from "@registry/components/ui/select";
import { useState } from "react";

export function SelectControlledExample() {
  const items = [
    { value: "typescript", label: "TypeScript" },
    { value: "python", label: "Python" },
    { value: "go", label: "Go" },
    { value: "rust", label: "Rust" },
  ];

  const [value, setValue] = useState("");

  return (
    <div className="flex flex-col gap-3">
      <Select items={items} value={value} onValueChange={(v) => setValue(v ?? "")}>
        <SelectTrigger variant="outline" className="w-44">
          <SelectValue placeholder="Pick a language" />
        </SelectTrigger>
        <SelectContent>
          {items.map((item) => (
            <SelectItem key={item.value} value={item.value}>
              {item.label}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
      {value && <p className="text-sm text-gray-500">Selected: {value}</p>}
    </div>
  );
}

With items prop

Pass an items array to Select so SelectValue renders the matching label instead of the raw value.

import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from "@registry/components/ui/select";

export function SelectWithItemsExample() {
  const items = [
    { value: "utc", label: "UTC" },
    { value: "est", label: "Eastern Time" },
    { value: "cst", label: "Central Time" },
    { value: "mst", label: "Mountain Time" },
    { value: "pst", label: "Pacific Time" },
    { value: "jst", label: "Japan Standard Time" },
  ];

  return (
    <div className="flex flex-wrap items-center gap-4">
      <Select items={items} defaultValue="utc">
        <SelectTrigger variant="outline" className="w-56">
          <SelectValue placeholder="Select time zone" />
        </SelectTrigger>
        <SelectContent>
          {items.map((item) => (
            <SelectItem key={item.value} value={item.value}>
              {item.label}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  );
}

Installation

Copy the source code below into your project:

import { Select as BaseSelect } from "@base-ui/react/select";
import { cn } from "@registry/lib/utils";
import { CaretDownIcon, CheckIcon } from "@phosphor-icons/react";
import type { VariantProps } from "class-variance-authority";

import { buttonVariants } from "./button";

function Select<T>(props: BaseSelect.Root.Props<T>) {
  return <BaseSelect.Root data-slot="select" {...props} />;
}

function SelectTrigger({
  children,
  className,
  variant = "outline",
  size = "default",
  ...props
}: BaseSelect.Trigger.Props & VariantProps<typeof buttonVariants>) {
  return (
    <BaseSelect.Trigger
      className={(state) =>
        cn(
          buttonVariants({ className, size, variant }),
          "justify-between gap-3 [&_svg]:transition-transform [&_svg]:duration-150",
          state.open && "[&_svg]:rotate-180",
          className,
        )
      }
      data-slot="select-trigger"
      {...props}
    >
      {children}

      <BaseSelect.Icon render={<CaretDownIcon className="size-4" />} />
    </BaseSelect.Trigger>
  );
}

function SelectValue(props: BaseSelect.Value.Props) {
  return <BaseSelect.Value data-slot="select-value" {...props} />;
}

function SelectContent({
  className,
  positionerProps,
  listProps,
  children,
  ...props
}: BaseSelect.Popup.Props & {
  positionerProps?: BaseSelect.Positioner.Props;
  listProps?: BaseSelect.List.Props;
}) {
  return (
    <BaseSelect.Portal>
      <BaseSelect.Positioner
        {...positionerProps}
        alignItemWithTrigger={positionerProps?.alignItemWithTrigger ?? false}
        sideOffset={8}
        className={cn(
          "z-10 min-w-(--anchor-width) outline-none select-none",
          positionerProps?.className,
        )}
      >
        <BaseSelect.Popup
          className={cn(
            "group origin-(--transform-origin) rounded-xl bg-popover bg-clip-padding text-popover-foreground shadow-lg outline outline-border transition-[transform,scale,opacity] data-ending-style:scale-90 data-ending-style:opacity-0 data-starting-style:scale-90 data-starting-style:opacity-0 data-[side=none]:data-ending-style:transition-none data-[side=none]:data-starting-style:scale-100 data-[side=none]:data-starting-style:opacity-100 data-[side=none]:data-starting-style:transition-none dark:shadow-none",
            className,
          )}
          data-slot="select-popup"
          {...props}
        >
          <BaseSelect.List
            className="relative grid max-h-(--available-height) scroll-py-6 grid-cols-[auto_1fr] overflow-y-auto p-1.5"
            data-slot="select-list"
            {...listProps}
          >
            {children}
          </BaseSelect.List>
        </BaseSelect.Popup>
      </BaseSelect.Positioner>
    </BaseSelect.Portal>
  );
}

function SelectItem({ className, children, ...props }: BaseSelect.Item.Props) {
  return (
    <BaseSelect.Item
      className={cn(
        "group/selectitem col-span-full grid min-w-(--anchor-width) cursor-default grid-cols-subgrid items-center gap-2 py-2 pr-4 pl-2.5 text-sm leading-4 outline-none select-none group-data-[side=none]:min-w-[calc(var(--anchor-width)+1rem)] group-data-[side=none]:pr-12 group-data-[side=none]:text-base group-data-[side=none]:leading-4 data-highlighted:relative data-highlighted:z-0 data-highlighted:text-primary-foreground data-highlighted:before:absolute data-highlighted:before:inset-0 data-highlighted:before:z-[-1] data-highlighted:before:rounded-lg data-highlighted:before:bg-primary pointer-coarse:py-2.5 pointer-coarse:text-[0.925rem]",
        className,
      )}
      data-slot="select-item"
      {...props}
    >
      <SelectItemIndicator />
      <BaseSelect.ItemText className="col-start-2" data-slot="select-item-text">
        {children}
      </BaseSelect.ItemText>
    </BaseSelect.Item>
  );
}

function SelectItemIndicator({ className, ...props }: BaseSelect.ItemIndicator.Props) {
  return (
    <div className="col-start-1 size-4">
      <BaseSelect.ItemIndicator
        className={className}
        data-slot="select-item-indicator"
        render={<CheckIcon />}
        {...props}
      />
    </div>
  );
}

function SelectSeparator(props: BaseSelect.Separator.Props) {
  return (
    <BaseSelect.Separator
      className="col-span-full my-1.5 h-px bg-border"
      data-slot="select-separator"
      {...props}
    />
  );
}

export { Select, SelectTrigger, SelectContent, SelectItem, SelectValue, SelectSeparator };

API Reference

Select

This component does not add any props on top of Base UI Select.Root . See the Base UI docs for the full API reference.

SelectTrigger

The SelectTrigger component extends the Base UI Select.Trigger props and adds the following:

PropTypeDefaultDescription
variant"default" | "secondary" | "outline" | "ghost" | "destructive" | "revert""outline"Visual style of the trigger button.
size"default" | "sm" | "lg" | "icon" | "icon-sm""default"Size of the trigger button.

SelectContent

The SelectContent component extends the Base UI Select.Popup props and adds the following:

PropTypeDefaultDescription
positionerPropsBaseSelect.Positioner.PropsProps forwarded to the underlying Positioner component. Includes align, side, sideOffset, alignOffset, collisionPadding, etc.
listPropsBaseSelect.List.PropsProps forwarded to the underlying List component.

SelectItem

This component does not add any props on top of Base UI Select.Item . See the Base UI docs for the full API reference.

SelectValue

This component does not add any props on top of Base UI Select.Value . See the Base UI docs for the full API reference.

SelectSeparator

This component does not add any props on top of Base UI Select.Separator . See the Base UI docs for the full API reference.