Select

Choose one or more values from a dropdown menu.

import { Select } from "@arshad/ui/components/select";

const fonts = [
  { label: "Inter", value: "inter" },
  { label: "JetBrains Mono", value: "jetbrains-mono" },
  { label: "Libre Baskerville", value: "libre-baskerville" },
];

export function Basic() {
  return (
    <Select.Root items={fonts}>
      <Select.Trigger aria-label="Font">
        <Select.Value placeholder="Select a font" />
        <Select.Icon />
      </Select.Trigger>
      <Select.Portal>
        <Select.Positioner>
          <Select.Popup>
            <Select.ScrollUpArrow />
            <Select.List>
              {fonts.map((font) => (
                <Select.Item key={font.value} value={font.value}>
                  <Select.ItemIndicator />
                  <Select.ItemText>{font.label}</Select.ItemText>
                </Select.Item>
              ))}
            </Select.List>
            <Select.ScrollDownArrow />
          </Select.Popup>
        </Select.Positioner>
      </Select.Portal>
    </Select.Root>
  );
}

A trigger opens a list of options and shows the chosen value.

Sizes

Three sizes to fit different layouts.

import { Select } from "@arshad/ui/components/select";

const themes = [
  { label: "System", value: "system" },
  { label: "Light", value: "light" },
  { label: "Dark", value: "dark" },
];

const sizes = ["sm", "md", "lg"] as const;

export function Sizes() {
  return (
    <div className="flex flex-wrap items-end gap-3">
      {sizes.map((size) => (
        <Select.Root key={size} items={themes} defaultValue="system">
          <Select.Trigger aria-label={`Font ${size}`} size={size}>
            <Select.Value />
            <Select.Icon />
          </Select.Trigger>
          <Select.Portal>
            <Select.Positioner>
              <Select.Popup>
                <Select.List>
                  {themes.map((theme) => (
                    <Select.Item key={theme.value} value={theme.value}>
                      <Select.ItemIndicator />
                      <Select.ItemText>{theme.label}</Select.ItemText>
                    </Select.Item>
                  ))}
                </Select.List>
              </Select.Popup>
            </Select.Positioner>
          </Select.Portal>
        </Select.Root>
      ))}
    </div>
  );
}

Multiple

Let users choose more than one option.

"use client";

import { Select } from "@arshad/ui/components/select";

const languages = [
  { label: "TypeScript", value: "ts" },
  { label: "Rust", value: "rust" },
  { label: "Go", value: "go" },
  { label: "Python", value: "py" },
  { label: "Swift", value: "swift" },
];

function renderValue(value: string[]) {
  if (value.length === 0) {
    return "Select languages";
  }
  if (value.length === 1) {
    return languages.find((language) => language.value === value[0])?.label;
  }
  return `${value.length} selected`;
}

export function Multiple() {
  return (
    <Select.Root items={languages} multiple defaultValue={["ts", "rust"]}>
      <Select.Trigger aria-label="Languages">
        <Select.Value>{renderValue}</Select.Value>
        <Select.Icon />
      </Select.Trigger>
      <Select.Portal>
        <Select.Positioner alignItemWithTrigger={false}>
          <Select.Popup>
            <Select.List>
              {languages.map((language) => (
                <Select.Item key={language.value} value={language.value}>
                  <Select.ItemIndicator />
                  <Select.ItemText>{language.label}</Select.ItemText>
                </Select.Item>
              ))}
            </Select.List>
          </Select.Popup>
        </Select.Positioner>
      </Select.Portal>
    </Select.Root>
  );
}

Grouped

Organize options into labeled sections.

import { Fragment } from "react";
import { Select } from "@arshad/ui/components/select";

const groups = [
  {
    value: "Fruits",
    items: [
      { label: "Apple", value: "apple" },
      { label: "Banana", value: "banana" },
      { label: "Orange", value: "orange" },
    ],
  },
  {
    value: "Vegetables",
    items: [
      { label: "Carrot", value: "carrot" },
      { label: "Spinach", value: "spinach" },
    ],
  },
];

export function Grouped() {
  return (
    <Select.Root items={groups}>
      <Select.Trigger aria-label="Produce">
        <Select.Value placeholder="Select produce" />
        <Select.Icon />
      </Select.Trigger>
      <Select.Portal>
        <Select.Positioner>
          <Select.Popup>
            <Select.List>
              {groups.map((group, index) => (
                <Fragment key={group.value}>
                  <Select.Group>
                    <Select.GroupLabel>{group.value}</Select.GroupLabel>
                    {group.items.map((item) => (
                      <Select.Item key={item.value} value={item.value}>
                        <Select.ItemIndicator />
                        <Select.ItemText>{item.label}</Select.ItemText>
                      </Select.Item>
                    ))}
                  </Select.Group>
                  {index < groups.length - 1 && <Select.Separator />}
                </Fragment>
              ))}
            </Select.List>
          </Select.Popup>
        </Select.Positioner>
      </Select.Portal>
    </Select.Root>
  );
}

Descriptions

Add supporting text below each option.

import { Select } from "@arshad/ui/components/select";

const plans = [
  { label: "Hobby", value: "hobby", description: "For personal projects and experiments" },
  { label: "Pro", value: "pro", description: "For small teams shipping to production" },
  { label: "Enterprise", value: "enterprise", description: "For organizations with custom needs" },
];

export function Descriptions() {
  return (
    <Select.Root items={plans}>
      <Select.Trigger aria-label="Plan">
        <Select.Value placeholder="Select a plan" />
        <Select.Icon />
      </Select.Trigger>
      <Select.Portal>
        <Select.Positioner alignItemWithTrigger={false}>
          <Select.Popup>
            <Select.List>
              {plans.map((plan) => (
                <Select.Item key={plan.value} value={plan.value}>
                  <Select.ItemIndicator />
                  <Select.ItemText>{plan.label}</Select.ItemText>
                  <Select.ItemDescription>{plan.description}</Select.ItemDescription>
                </Select.Item>
              ))}
            </Select.List>
          </Select.Popup>
        </Select.Positioner>
      </Select.Portal>
    </Select.Root>
  );
}

Icons

Show an icon next to each option.

import { Icon } from "@arshad/ui/components/icon";
import { Select } from "@arshad/ui/components/select";

const appearances = [
  {
    value: "system",
    label: (
      <>
        <Icon name="Monitor" />
        System
      </>
    ),
  },
  {
    value: "light",
    label: (
      <>
        <Icon name="Sun" />
        Light
      </>
    ),
  },
  {
    value: "dark",
    label: (
      <>
        <Icon name="Moon" />
        Dark
      </>
    ),
  },
];

export function Icons() {
  return (
    <Select.Root items={appearances} defaultValue="system">
      <Select.Trigger aria-label="Theme">
        <Select.Value />
        <Select.Icon />
      </Select.Trigger>
      <Select.Portal>
        <Select.Positioner>
          <Select.Popup>
            <Select.List>
              {appearances.map((appearance) => (
                <Select.Item key={appearance.value} value={appearance.value}>
                  <Select.ItemIndicator />
                  <Select.ItemText>{appearance.label}</Select.ItemText>
                </Select.Item>
              ))}
            </Select.List>
          </Select.Popup>
        </Select.Positioner>
      </Select.Portal>
    </Select.Root>
  );
}

Avatars

Show an avatar next to each option.

import { Avatar } from "@arshad/ui/components/avatar";
import { Select } from "@arshad/ui/components/select";

const people = [
  { value: "lara", name: "Lara Tucci", initials: "LT", src: "/avatars/lara.webp" },
  { value: "devon", name: "Devon Lane", initials: "DL", src: "/avatars/devon.webp" },
  { value: "noah", name: "Noah Pierre", initials: "NP", src: "/avatars/noah.webp" },
];

const items = people.map((person) => ({
  value: person.value,
  label: (
    <>
      <Avatar.Root aria-hidden>
        <Avatar.Image src={person.src} alt="" />
        <Avatar.Fallback>{person.initials}</Avatar.Fallback>
      </Avatar.Root>
      {person.name}
    </>
  ),
}));

export function Avatars() {
  return (
    <Select.Root items={items} defaultValue="lara">
      <Select.Trigger aria-label="Assignee">
        <Select.Value />
        <Select.Icon />
      </Select.Trigger>
      <Select.Portal>
        <Select.Positioner>
          <Select.Popup>
            <Select.List>
              {items.map((item) => (
                <Select.Item key={item.value} value={item.value}>
                  <Select.ItemIndicator />
                  <Select.ItemText>{item.label}</Select.ItemText>
                </Select.Item>
              ))}
            </Select.List>
          </Select.Popup>
        </Select.Positioner>
      </Select.Portal>
    </Select.Root>
  );
}

Disabled

Disable the whole control or individual options.

import { Select } from "@arshad/ui/components/select";

const plans = [
  { label: "Hobby", value: "hobby" },
  { label: "Pro", value: "pro" },
  { label: "Enterprise", value: "enterprise" },
];

export function Disabled() {
  return (
    <div className="flex flex-wrap items-center gap-3">
      {/* The whole control is disabled. */}
      <Select.Root items={plans} defaultValue="pro" disabled>
        <Select.Trigger aria-label="Size">
          <Select.Value />
          <Select.Icon />
        </Select.Trigger>
        <Select.Portal>
          <Select.Positioner>
            <Select.Popup>
              <Select.List>
                {plans.map((plan) => (
                  <Select.Item key={plan.value} value={plan.value}>
                    <Select.ItemIndicator />
                    <Select.ItemText>{plan.label}</Select.ItemText>
                  </Select.Item>
                ))}
              </Select.List>
            </Select.Popup>
          </Select.Positioner>
        </Select.Portal>
      </Select.Root>

      {/* A single option is disabled. */}
      <Select.Root items={plans} defaultValue="hobby">
        <Select.Trigger aria-label="Size">
          <Select.Value />
          <Select.Icon />
        </Select.Trigger>
        <Select.Portal>
          <Select.Positioner>
            <Select.Popup>
              <Select.List>
                {plans.map((plan) => (
                  <Select.Item
                    key={plan.value}
                    value={plan.value}
                    disabled={plan.value === "enterprise"}
                  >
                    <Select.ItemIndicator />
                    <Select.ItemText>{plan.label}</Select.ItemText>
                  </Select.Item>
                ))}
              </Select.List>
            </Select.Popup>
          </Select.Positioner>
        </Select.Portal>
      </Select.Root>
    </div>
  );
}

Invalid

Shows an error state when the value is invalid.

import { Select } from "@arshad/ui/components/select";

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

export function Invalid() {
  return (
    <Select.Root items={sizes}>
      <Select.Trigger aria-label="Size" data-invalid>
        <Select.Value placeholder="Select a size" />
        <Select.Icon />
      </Select.Trigger>
      <Select.Portal>
        <Select.Positioner>
          <Select.Popup>
            <Select.List>
              {sizes.map((size) => (
                <Select.Item key={size.value} value={size.value}>
                  <Select.ItemIndicator />
                  <Select.ItemText>{size.label}</Select.ItemText>
                </Select.Item>
              ))}
            </Select.List>
          </Select.Popup>
        </Select.Positioner>
      </Select.Portal>
    </Select.Root>
  );
}

API

Select.Trigger

PropTypeDefault
size"sm" | "md" | "lg""md"

Select.Positioner

PropTypeDefault
sideOffsetnumber4

Plus everything from Base UI Select, including items, value, defaultValue, and onValueChange. Each part wraps the Base UI part of the same name, so alignItemWithTrigger, side, and the other positioning props go on Select.Positioner.