Vela UI Docsvenizia.ai

Select

Displays a list of options for the user to pick from, triggered by a button.

npx shadcn@latest add @velaui/select
npx shadcn@latest add @velaui/select

Examples

  • Default
  • With label
  • With dot
  • With icon
  • With avatar
  • Helper text
  • Invalid
  • Disabled
  • Grouped
  • Content position

View code

Select · Default

Installation

pnpm dlx velaui add @velaui/select
pnpm dlx velaui add @velaui/select

Code

select-default.tsx
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";

export default function Example() {
  return (
    <div className="w-full max-w-xs">
      <Select>
        <SelectTrigger>
          <SelectValue placeholder="Select item" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="linden">Linden Supply Co.</SelectItem>
          <SelectItem value="harbor">Harbor &amp; Pine</SelectItem>
          <SelectItem value="tanaka">Tanaka Freight</SelectItem>
        </SelectContent>
      </Select>
    </div>
  );
}
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";

const ITEMS = {
  linden: "Linden Supply Co.",
  harbor: "Harbor & Pine",
  tanaka: "Tanaka Freight",
};

export default function Example() {
  return (
    <div className="w-full max-w-xs">
      <Select items={ITEMS}>
        <SelectTrigger>
          <SelectValue placeholder="Select item" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="linden">Linden Supply Co.</SelectItem>
          <SelectItem value="harbor">Harbor &amp; Pine</SelectItem>
          <SelectItem value="tanaka">Tanaka Freight</SelectItem>
        </SelectContent>
      </Select>
    </div>
  );
}

View code

Select · With label

Installation

pnpm dlx velaui add @velaui/select
pnpm dlx velaui add @velaui/select

Code

select-with-label.tsx
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useId } from "react";

export default function Example() {
  const id = useId();

  return (
    <div className="w-full max-w-xs flex flex-col gap-2">
      <Label htmlFor={id}>Select with label</Label>
      <Select>
        <SelectTrigger id={id}>
          <SelectValue placeholder="Choose an option" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="mai">Mai Tran</SelectItem>
          <SelectItem value="kenji">Kenji Sato</SelectItem>
          <SelectItem value="priya">Priya Nair</SelectItem>
        </SelectContent>
      </Select>
    </div>
  );
}
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useId } from "react";

const ITEMS = {
  mai: "Mai Tran",
  kenji: "Kenji Sato",
  priya: "Priya Nair",
};

export default function Example() {
  const id = useId();

  return (
    <div className="w-full max-w-xs flex flex-col gap-2">
      <Label htmlFor={id}>Select with label</Label>
      <Select items={ITEMS}>
        <SelectTrigger id={id}>
          <SelectValue placeholder="Choose an option" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="mai">Mai Tran</SelectItem>
          <SelectItem value="kenji">Kenji Sato</SelectItem>
          <SelectItem value="priya">Priya Nair</SelectItem>
        </SelectContent>
      </Select>
    </div>
  );
}

View code

Select · With dot

Installation

pnpm dlx velaui add @velaui/select
pnpm dlx velaui add @velaui/select

Code

select-with-dot.tsx
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useId } from "react";

const PRIORITIES = [
  { value: "high", label: "High", dot: "bg-negative-middle" },
  { value: "medium", label: "Medium", dot: "bg-warning-middle" },
  { value: "low", label: "Low", dot: "bg-muted-foreground" },
];

export default function Example() {
  const id = useId();

  return (
    <div className="w-full max-w-xs flex flex-col gap-2">
      <Label htmlFor={id}>Select with status dot</Label>
      <Select defaultValue="high">
        <SelectTrigger id={id}>
          <SelectValue placeholder="Set priority" />
        </SelectTrigger>
        <SelectContent>
          {PRIORITIES.map(({ value, label, dot }) => (
            <SelectItem key={value} value={value}>
              <div className={`size-2 shrink-0 rounded-full ${dot}`} />
              {label}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  );
}
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useId } from "react";

const PRIORITIES = [
  { value: "high", label: "High", dot: "bg-negative-middle" },
  { value: "medium", label: "Medium", dot: "bg-warning-middle" },
  { value: "low", label: "Low", dot: "bg-muted-foreground" },
];

const ITEMS = PRIORITIES.map(({ value, label, dot }) => ({
  value,
  label: (
    <>
      <span className={`size-2 shrink-0 rounded-full ${dot}`} />
      {label}
    </>
  ),
}));

export default function Example() {
  const id = useId();

  return (
    <div className="w-full max-w-xs flex flex-col gap-2">
      <Label htmlFor={id}>Select with status dot</Label>
      <Select defaultValue="high" items={ITEMS}>
        <SelectTrigger id={id}>
          <SelectValue placeholder="Set priority" />
        </SelectTrigger>
        <SelectContent>
          {PRIORITIES.map(({ value, label, dot }) => (
            <SelectItem key={value} value={value}>
              <div className={`size-2 shrink-0 rounded-full ${dot}`} />
              {label}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  );
}

View code

Select · With icon

Installation

pnpm dlx velaui add @velaui/select
pnpm dlx velaui add @velaui/select

Code

select-with-icon.tsx
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { File02 } from "@/components/icons/file-02";
import { File06 } from "@/components/icons/file-06";
import { FileCode } from "@/components/icons/file-code";
import { useId } from "react";

const FORMATS = [
  { value: "pdf", label: "PDF document", Icon: File02 },
  { value: "csv", label: "Spreadsheet", Icon: File06 },
  { value: "json", label: "JSON", Icon: FileCode },
];

export default function Example() {
  const id = useId();

  return (
    <div className="w-full max-w-xs flex flex-col gap-2">
      <Label htmlFor={id}>Select with icon</Label>
      <Select>
        <SelectTrigger id={id}>
          <SelectValue placeholder="Choose an export format" />
        </SelectTrigger>
        <SelectContent>
          {FORMATS.map(({ value, label, Icon }) => (
            <SelectItem key={value} value={value}>
              <Icon className="size-4" />
              {label}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  );
}
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { File02 } from "@/components/icons/file-02";
import { File06 } from "@/components/icons/file-06";
import { FileCode } from "@/components/icons/file-code";
import { useId } from "react";

const FORMATS = [
  { value: "pdf", label: "PDF document", Icon: File02 },
  { value: "csv", label: "Spreadsheet", Icon: File06 },
  { value: "json", label: "JSON", Icon: FileCode },
];

const ITEMS = FORMATS.map(({ value, label, Icon }) => ({
  value,
  label: (
    <>
      <Icon className="size-4" />
      {label}
    </>
  ),
}));

export default function Example() {
  const id = useId();

  return (
    <div className="w-full max-w-xs flex flex-col gap-2">
      <Label htmlFor={id}>Select with icon</Label>
      <Select items={ITEMS}>
        <SelectTrigger id={id}>
          <SelectValue placeholder="Choose an export format" />
        </SelectTrigger>
        <SelectContent>
          {FORMATS.map(({ value, label, Icon }) => (
            <SelectItem key={value} value={value}>
              <Icon className="size-4" />
              {label}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  );
}

View code

Select · With avatar

Installation

pnpm dlx velaui add @velaui/select
pnpm dlx velaui add @velaui/select

Code

select-with-avatar.tsx
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
} from "@/components/ui/avatar";
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useId } from "react";

const REVIEWERS = [
  {
    value: "mai",
    name: "Mai Tran",
    initials: "MT",
    avatar: "https://github.com/shadcn.png",
  },
  {
    value: "kenji",
    name: "Kenji Sato",
    initials: "KS",
    avatar: "https://github.com/pranathip.png",
  },
  {
    value: "priya",
    name: "Priya Nair",
    initials: "PN",
    avatar: "https://github.com/maxleiter.png",
  },
];

export default function Example() {
  const id = useId();

  return (
    <div className="w-full max-w-xs flex flex-col gap-2">
      <Label htmlFor={id}>Select with avatar</Label>
      <Select defaultValue="mai">
        <SelectTrigger id={id}>
          <SelectValue placeholder="Pick a reviewer" />
        </SelectTrigger>
        <SelectContent>
          {REVIEWERS.map((reviewer) => (
            <SelectItem key={reviewer.value} value={reviewer.value}>
              <Avatar size={"2xs"} className="shrink-0">
                <AvatarImage src={reviewer.avatar} alt={reviewer.name} />
                <AvatarFallback>{reviewer.initials}</AvatarFallback>
              </Avatar>
              {reviewer.name}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  );
}
import {
  Avatar,
  AvatarFallback,
  AvatarImage,
} from "@/components/ui/avatar";
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useId } from "react";

const REVIEWERS = [
  {
    value: "mai",
    name: "Mai Tran",
    initials: "MT",
    avatar: "https://github.com/shadcn.png",
  },
  {
    value: "kenji",
    name: "Kenji Sato",
    initials: "KS",
    avatar: "https://github.com/pranathip.png",
  },
  {
    value: "priya",
    name: "Priya Nair",
    initials: "PN",
    avatar: "https://github.com/maxleiter.png",
  },
];

const ITEMS = REVIEWERS.map((reviewer) => ({
  value: reviewer.value,
  label: (
    <>
      <Avatar size={"2xs"} className="shrink-0">
        <AvatarImage src={reviewer.avatar} alt={reviewer.name} />
        <AvatarFallback>{reviewer.initials}</AvatarFallback>
      </Avatar>
      {reviewer.name}
    </>
  ),
}));

export default function Example() {
  const id = useId();

  return (
    <div className="w-full max-w-xs flex flex-col gap-2">
      <Label htmlFor={id}>Select with avatar</Label>
      <Select defaultValue="mai" items={ITEMS}>
        <SelectTrigger id={id}>
          <SelectValue placeholder="Pick a reviewer" />
        </SelectTrigger>
        <SelectContent>
          {REVIEWERS.map((reviewer) => (
            <SelectItem key={reviewer.value} value={reviewer.value}>
              <Avatar size={"2xs"} className="shrink-0">
                <AvatarImage src={reviewer.avatar} alt={reviewer.name} />
                <AvatarFallback>{reviewer.initials}</AvatarFallback>
              </Avatar>
              {reviewer.name}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  );
}

View code

Select · Helper text

Installation

pnpm dlx velaui add @velaui/select
pnpm dlx velaui add @velaui/select

Code

select-helper-text.tsx
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useId } from "react";

export default function Example() {
  const id = useId();

  return (
    <div className="w-full max-w-xs flex flex-col gap-2">
      <Label htmlFor={id}>Select with helper text</Label>
      <Select>
        <SelectTrigger id={id}>
          <SelectValue placeholder="Select a region" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="eu">European Union</SelectItem>
          <SelectItem value="us">United States</SelectItem>
          <SelectItem value="apac">Asia Pacific</SelectItem>
        </SelectContent>
      </Select>
      <p className="text-muted-foreground text-xs">
        Tax is calculated from your billing region.
      </p>
    </div>
  );
}
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useId } from "react";

const ITEMS = {
  eu: "European Union",
  us: "United States",
  apac: "Asia Pacific",
};

export default function Example() {
  const id = useId();

  return (
    <div className="w-full max-w-xs flex flex-col gap-2">
      <Label htmlFor={id}>Select with helper text</Label>
      <Select items={ITEMS}>
        <SelectTrigger id={id}>
          <SelectValue placeholder="Select a region" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="eu">European Union</SelectItem>
          <SelectItem value="us">United States</SelectItem>
          <SelectItem value="apac">Asia Pacific</SelectItem>
        </SelectContent>
      </Select>
      <p className="text-muted-foreground text-xs">
        Tax is calculated from your billing region.
      </p>
    </div>
  );
}

View code

Select · Invalid

Installation

pnpm dlx velaui add @velaui/select
pnpm dlx velaui add @velaui/select

Code

select-invalid.tsx
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useId } from "react";

export default function Example() {
  const id = useId();

  return (
    <div className="w-full max-w-xs flex flex-col gap-2">
      <Label htmlFor={id}>Invalid select</Label>
      <Select>
        <SelectTrigger id={id} aria-invalid>
          <SelectValue placeholder="Select a plan" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="starter">Starter</SelectItem>
          <SelectItem value="growth">Growth</SelectItem>
          <SelectItem value="enterprise">Enterprise</SelectItem>
        </SelectContent>
      </Select>
      <p className="text-destructive text-xs">Pick a plan to continue</p>
    </div>
  );
}
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useId } from "react";

const ITEMS = {
  starter: "Starter",
  growth: "Growth",
  enterprise: "Enterprise",
};

export default function Example() {
  const id = useId();

  return (
    <div className="w-full max-w-xs flex flex-col gap-2">
      <Label htmlFor={id}>Invalid select</Label>
      <Select items={ITEMS}>
        <SelectTrigger id={id} aria-invalid>
          <SelectValue placeholder="Select a plan" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="starter">Starter</SelectItem>
          <SelectItem value="growth">Growth</SelectItem>
          <SelectItem value="enterprise">Enterprise</SelectItem>
        </SelectContent>
      </Select>
      <p className="text-destructive text-xs">Pick a plan to continue</p>
    </div>
  );
}

View code

Select · Disabled

Installation

pnpm dlx velaui add @velaui/select
pnpm dlx velaui add @velaui/select

Code

select-disabled.tsx
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useId } from "react";

export default function Example() {
  const id = useId();

  return (
    <div className="w-full max-w-xs flex flex-col gap-2">
      <Label htmlFor={id}>Disabled select</Label>
      <Select disabled defaultValue="enterprise">
        <SelectTrigger id={id}>
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="enterprise">Enterprise</SelectItem>
        </SelectContent>
      </Select>
    </div>
  );
}
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useId } from "react";

const ITEMS = { enterprise: "Enterprise" };

export default function Example() {
  const id = useId();

  return (
    <div className="w-full max-w-xs flex flex-col gap-2">
      <Label htmlFor={id}>Disabled select</Label>
      <Select disabled defaultValue="enterprise" items={ITEMS}>
        <SelectTrigger id={id}>
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="enterprise">Enterprise</SelectItem>
        </SelectContent>
      </Select>
    </div>
  );
}

View code

Select · Grouped

Installation

pnpm dlx velaui add @velaui/select
pnpm dlx velaui add @velaui/select

Code

select-grouped.tsx
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useId } from "react";

export default function Example() {
  const id = useId();

  return (
    <div className="w-full max-w-xs flex flex-col gap-2">
      <Label htmlFor={id}>Grouped select</Label>
      <Select>
        <SelectTrigger id={id}>
          <SelectValue placeholder="Select a country" />
        </SelectTrigger>
        <SelectContent>
          <SelectGroup>
            <SelectLabel>Europe</SelectLabel>
            <SelectItem value="de">Germany</SelectItem>
            <SelectItem value="pt">Portugal</SelectItem>
          </SelectGroup>
          <SelectSeparator />
          <SelectGroup>
            <SelectLabel>Asia</SelectLabel>
            <SelectItem value="jp">Japan</SelectItem>
            <SelectItem value="vn">Vietnam</SelectItem>
          </SelectGroup>
        </SelectContent>
      </Select>
    </div>
  );
}
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useId } from "react";

const ITEMS = [
  { value: "de", label: "Germany" },
  { value: "pt", label: "Portugal" },
  { value: "jp", label: "Japan" },
  { value: "vn", label: "Vietnam" },
];

export default function Example() {
  const id = useId();

  return (
    <div className="w-full max-w-xs flex flex-col gap-2">
      <Label htmlFor={id}>Grouped select</Label>
      <Select items={ITEMS}>
        <SelectTrigger id={id}>
          <SelectValue placeholder="Select a country" />
        </SelectTrigger>
        <SelectContent>
          <SelectGroup>
            <SelectLabel>Europe</SelectLabel>
            <SelectItem value="de">Germany</SelectItem>
            <SelectItem value="pt">Portugal</SelectItem>
          </SelectGroup>
          <SelectSeparator />
          <SelectGroup>
            <SelectLabel>Asia</SelectLabel>
            <SelectItem value="jp">Japan</SelectItem>
            <SelectItem value="vn">Vietnam</SelectItem>
          </SelectGroup>
        </SelectContent>
      </Select>
    </div>
  );
}

View code

Select · Content position

Installation

pnpm dlx velaui add @velaui/select
pnpm dlx velaui add @velaui/select

Code

select-content-position.tsx
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useId } from "react";

const POSITIONS = [
  { position: "item-aligned", label: "Item aligned content (default)" },
  { position: "popper", label: "Popper content" },
] as const;

export default function Example() {
  const id = useId();

  return (
    <div className="w-full max-w-xs flex flex-col gap-4">
      {POSITIONS.map(({ position, label }) => (
        <div key={position} className="flex flex-col gap-2">
          <Label htmlFor={`${id}-${position}`}>{label}</Label>
          <Select defaultValue="staging">
            <SelectTrigger id={`${id}-${position}`}>
              <SelectValue placeholder="Choose an environment" />
            </SelectTrigger>
            <SelectContent position={position}>
              <SelectItem value="development">Development</SelectItem>
              <SelectItem value="staging">Staging</SelectItem>
              <SelectItem value="production">Production</SelectItem>
            </SelectContent>
          </Select>
        </div>
      ))}
    </div>
  );
}
import { Label } from "@/components/ui/label";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { useId } from "react";

const POSITIONS = [
  { position: "item-aligned", label: "Item aligned content (default)" },
  { position: "popper", label: "Popper content" },
] as const;

const ITEMS = {
  development: "Development",
  staging: "Staging",
  production: "Production",
};

export default function Example() {
  const id = useId();

  return (
    <div className="w-full max-w-xs flex flex-col gap-4">
      {POSITIONS.map(({ position, label }) => (
        <div key={position} className="flex flex-col gap-2">
          <Label htmlFor={`${id}-${position}`}>{label}</Label>
          <Select defaultValue="staging" items={ITEMS}>
            <SelectTrigger id={`${id}-${position}`}>
              <SelectValue placeholder="Choose an environment" />
            </SelectTrigger>
            <SelectContent position={position}>
              <SelectItem value="development">Development</SelectItem>
              <SelectItem value="staging">Staging</SelectItem>
              <SelectItem value="production">Production</SelectItem>
            </SelectContent>
          </Select>
        </div>
      ))}
    </div>
  );
}

Sizes

Usage

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

export default function Example() {
  return (
    <div className="w-full max-w-xs">
      <Select>
        <SelectTrigger>
          <SelectValue placeholder="Select item" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="linden">Linden Supply Co.</SelectItem>
          <SelectItem value="harbor">Harbor &amp; Pine</SelectItem>
          <SelectItem value="tanaka">Tanaka Freight</SelectItem>
        </SelectContent>
      </Select>
    </div>
  );
}
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";

const ITEMS = {
  linden: "Linden Supply Co.",
  harbor: "Harbor & Pine",
  tanaka: "Tanaka Freight",
};

export default function Example() {
  return (
    <div className="w-full max-w-xs">
      <Select items={ITEMS}>
        <SelectTrigger>
          <SelectValue placeholder="Select item" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="linden">Linden Supply Co.</SelectItem>
          <SelectItem value="harbor">Harbor &amp; Pine</SelectItem>
          <SelectItem value="tanaka">Tanaka Freight</SelectItem>
        </SelectContent>
      </Select>
    </div>
  );
}

Props

Select

Takes every prop of SelectProps.

SelectContent

PropTypeDefault
align"center" | "start" | "end""center"
position"item-aligned" | "popper""item-aligned"

Takes these and every prop of SelectContentProps.

SelectGroup

Takes every prop of SelectGroupProps.

SelectItem

Takes every prop of SelectItemProps.

SelectLabel

Takes every prop of SelectLabelProps.

SelectScrollDownButton

Takes every prop of SelectScrollDownButtonProps.

SelectScrollUpButton

Takes every prop of SelectScrollUpButtonProps.

SelectSeparator

Takes every prop of SelectSeparatorProps.

SelectTrigger

PropTypeDefault
size"sm" | "md" | "lg""sm"
variant"fill" | "underline" | "outlined""outlined"

Takes these and every prop of SelectTriggerProps.

SelectValue

Takes every prop of SelectValueProps.

SelectContent

PropTypeDefault
alignimport("/app/node_modules/.bun/@base-ui+react@1.6.0+df7f6a806acd27ca/node_modules/@base-ui/react/utils/useAnchorPositioning").Align"center"
position"item-aligned" | "popper""item-aligned"

Takes these and every prop of SelectContentProps.

SelectGroup

Takes every prop of SelectGroupProps.

SelectItem

Takes every prop of SelectItemProps.

SelectLabel

Takes every prop of SelectLabelProps.

SelectScrollDownButton

Takes every prop of SelectScrollDownButtonProps.

SelectScrollUpButton

Takes every prop of SelectScrollUpButtonProps.

SelectSeparator

Takes every prop of SelectSeparatorProps.

SelectTrigger

PropTypeDefault
size"sm" | "md" | "lg""sm"
variant"underline" | "fill" | "outlined""outlined"

Takes these and every prop of SelectTriggerProps.

SelectValue

Takes every prop of SelectValueProps.

Dependencies

npm
class-variance-authority@0.7.1radix-ui@1.6.0
Installs with it
3 icons
npm
@base-ui/react@1.6.0class-variance-authority@0.7.1
Installs with it
3 icons

Source

components/ui/select.tsx
/* eslint-disable react-refresh/only-export-components */
import * as React from "react";
import { Select as SelectPrimitive } from "radix-ui";
import { cva, type VariantProps } from "class-variance-authority";
import { ChevronDown } from "@/components/icons/chevron-down";
import { Check01 } from "@/components/icons/check-01";
import { ChevronUp } from "@/components/icons/chevron-up";

import { cn } from "@/lib/utils";

const selectTriggerVariants = cva(
  "flex w-full items-center justify-between gap-2 text-sm leading-[1.4] text-text-strong whitespace-nowrap transition-colors outline-none select-none disabled:pointer-events-none disabled:cursor-not-allowed disabled:text-text-disabled data-placeholder:text-text-note *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3",
  {
    variants: {
      variant: {
        outlined:
          "rounded-md border border-border-default bg-bg-main hover:border-border-hover hover:bg-input-hover focus-visible:border-ring-neutral-outline focus-visible:bg-input-default focus-visible:ring-3 focus-visible:ring-ring-neutral-shadow disabled:border-border-disabled disabled:bg-input-disabled aria-invalid:border-negative-middle aria-invalid:focus-visible:border-negative-middle aria-invalid:focus-visible:ring-negative-middle/25",
        // Fill draws no border at rest, but hover/focus/invalid all draw a 1px
        // one — reserve it as transparent so entering a state never reflows.
        fill: "rounded-md border border-transparent bg-input-default hover:border-border-hover hover:bg-input-hover focus-visible:border-ring-neutral-outline focus-visible:ring-3 focus-visible:ring-ring-neutral-shadow disabled:border-transparent disabled:bg-input-disabled aria-invalid:border-negative-middle aria-invalid:focus-visible:border-negative-middle aria-invalid:focus-visible:ring-negative-middle/25",
        // Underline carries no surface, no radius and no focus ring — the whole
        // state vocabulary lives on the bottom border.
        underline:
          "rounded-none border-0 border-b border-b-border-default bg-transparent hover:border-b-border-active focus-visible:border-b-border-active disabled:border-b-border-default disabled:bg-transparent aria-invalid:border-b-negative-middle aria-invalid:focus-visible:border-b-negative-middle",
      },
      size: {
        sm: "h-8 px-2.5 py-1.5",
        md: "h-10 px-3 py-2.5",
        lg: "h-12 px-4 py-3.5",
      },
    },
    compoundVariants: [
      {
        variant: "underline",
        size: ["sm", "md", "lg"],
        className: "px-0",
      },
    ],
    defaultVariants: {
      variant: "outlined",
      size: "sm",
    },
  },
);

export type SelectProps = React.ComponentProps<typeof SelectPrimitive.Root>;

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

export type SelectGroupProps = React.ComponentProps<typeof SelectPrimitive.Group>;

function SelectGroup({
  className,
  ...props
}: SelectGroupProps) {
  return (
    <SelectPrimitive.Group
      data-slot="select-group"
      className={cn("flex scroll-my-1 flex-col gap-1", className)}
      {...props}
    />
  );
}

export type SelectValueProps = React.ComponentProps<typeof SelectPrimitive.Value>;

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

export type SelectTriggerProps = React.ComponentProps<
  typeof SelectPrimitive.Trigger
> &
  VariantProps<typeof selectTriggerVariants>;

function SelectTrigger({
  className,
  variant = "outlined",
  size = "sm",
  children,
  ...props
}: SelectTriggerProps) {
  return (
    <SelectPrimitive.Trigger
      data-slot="select-trigger"
      data-variant={variant}
      data-size={size}
      className={cn(selectTriggerVariants({ variant, size }), className)}
      {...props}
    >
      {children}
      <SelectPrimitive.Icon asChild>
        <ChevronDown className="pointer-events-none size-4 text-icon-muted" />
      </SelectPrimitive.Icon>
    </SelectPrimitive.Trigger>
  );
}

export type SelectContentProps = React.ComponentProps<typeof SelectPrimitive.Content>;

function SelectContent({
  className,
  children,
  position = "item-aligned",
  align = "center",
  ...props
}: SelectContentProps) {
  return (
    <SelectPrimitive.Portal>
      <SelectPrimitive.Content
        data-slot="select-content"
        data-align-trigger={position === "item-aligned"}
        className={cn(
          "relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border border-border-default bg-bg-main p-1 text-text-strong shadow-md duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
          position === "popper" &&
            "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
          className,
        )}
        position={position}
        align={align}
        {...props}
      >
        <SelectScrollUpButton />
        <SelectPrimitive.Viewport
          data-position={position}
          className={cn(
            "flex flex-col gap-1 data-[position=popper]:h-(--radix-select-trigger-height) data-[position=popper]:w-full data-[position=popper]:min-w-(--radix-select-trigger-width)",
          )}
        >
          {children}
        </SelectPrimitive.Viewport>
        <SelectScrollDownButton />
      </SelectPrimitive.Content>
    </SelectPrimitive.Portal>
  );
}

export type SelectLabelProps = React.ComponentProps<typeof SelectPrimitive.Label>;

function SelectLabel({
  className,
  ...props
}: SelectLabelProps) {
  return (
    <SelectPrimitive.Label
      data-slot="select-label"
      className={cn("px-2 py-1 text-xs text-text-muted", className)}
      {...props}
    />
  );
}

export type SelectItemProps = React.ComponentProps<typeof SelectPrimitive.Item>;

function SelectItem({
  className,
  children,
  ...props
}: SelectItemProps) {
  return (
    <SelectPrimitive.Item
      data-slot="select-item"
      className={cn(
        "relative flex w-full cursor-default items-center gap-2 rounded-md py-1.5 pr-10 pl-2 text-sm leading-[1.4] text-text-strong outline-hidden select-none focus:bg-bg-surface-1 data-[state=checked]:bg-bg-surface-1 data-disabled:pointer-events-none data-disabled:text-text-disabled [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-5 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
        className,
      )}
      {...props}
    >
      <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center">
        <SelectPrimitive.ItemIndicator>
          <Check01 className="pointer-events-none text-icon-strong size-4" />
        </SelectPrimitive.ItemIndicator>
      </span>
      <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
    </SelectPrimitive.Item>
  );
}

export type SelectSeparatorProps = React.ComponentProps<typeof SelectPrimitive.Separator>;

function SelectSeparator({
  className,
  ...props
}: SelectSeparatorProps) {
  return (
    <SelectPrimitive.Separator
      data-slot="select-separator"
      className={cn(
        "pointer-events-none -mx-1 my-1 h-px bg-border-default",
        className,
      )}
      {...props}
    />
  );
}

export type SelectScrollUpButtonProps = React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>;

function SelectScrollUpButton({
  className,
  ...props
}: SelectScrollUpButtonProps) {
  return (
    <SelectPrimitive.ScrollUpButton
      data-slot="select-scroll-up-button"
      className={cn(
        "z-10 flex cursor-default items-center justify-center bg-bg-main py-1 text-icon-muted [&_svg:not([class*='size-'])]:size-3",
        className,
      )}
      {...props}
    >
      <ChevronUp />
    </SelectPrimitive.ScrollUpButton>
  );
}

export type SelectScrollDownButtonProps = React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>;

function SelectScrollDownButton({
  className,
  ...props
}: SelectScrollDownButtonProps) {
  return (
    <SelectPrimitive.ScrollDownButton
      data-slot="select-scroll-down-button"
      className={cn(
        "z-10 flex cursor-default items-center justify-center bg-bg-main py-1 text-icon-muted [&_svg:not([class*='size-'])]:size-3",
        className,
      )}
      {...props}
    >
      <ChevronDown />
    </SelectPrimitive.ScrollDownButton>
  );
}

export {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectScrollDownButton,
  SelectScrollUpButton,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
  selectTriggerVariants,
};
components/ui/select.tsx
/* eslint-disable react-refresh/only-export-components */
import * as React from "react";
import { Select as SelectPrimitive } from "@base-ui/react/select";
import { cva, type VariantProps } from "class-variance-authority";
import { ChevronDown } from "@/components/icons/chevron-down";
import { Check01 } from "@/components/icons/check-01";
import { ChevronUp } from "@/components/icons/chevron-up";

import { cn } from "@/lib/utils";

const selectTriggerVariants = cva(
  "flex w-full items-center justify-between gap-2 text-sm leading-[1.4] text-text-strong whitespace-nowrap transition-colors outline-none select-none disabled:pointer-events-none disabled:cursor-not-allowed disabled:text-text-disabled data-placeholder:text-text-note *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3",
  {
    variants: {
      variant: {
        outlined:
          "rounded-md border border-border-default bg-bg-main hover:border-border-hover hover:bg-input-hover focus-visible:border-ring-neutral-outline focus-visible:bg-input-default focus-visible:ring-3 focus-visible:ring-ring-neutral-shadow disabled:border-border-disabled disabled:bg-input-disabled aria-invalid:border-negative-middle aria-invalid:focus-visible:border-negative-middle aria-invalid:focus-visible:ring-negative-middle/25",
        // Fill draws no border at rest, but hover/focus/invalid all draw a 1px
        // one — reserve it as transparent so entering a state never reflows.
        fill: "rounded-md border border-transparent bg-input-default hover:border-border-hover hover:bg-input-hover focus-visible:border-ring-neutral-outline focus-visible:ring-3 focus-visible:ring-ring-neutral-shadow disabled:border-transparent disabled:bg-input-disabled aria-invalid:border-negative-middle aria-invalid:focus-visible:border-negative-middle aria-invalid:focus-visible:ring-negative-middle/25",
        // Underline carries no surface, no radius and no focus ring — the whole
        // state vocabulary lives on the bottom border.
        underline:
          "rounded-none border-0 border-b border-b-border-default bg-transparent hover:border-b-border-active focus-visible:border-b-border-active disabled:border-b-border-default disabled:bg-transparent aria-invalid:border-b-negative-middle aria-invalid:focus-visible:border-b-negative-middle",
      },
      size: {
        sm: "h-8 px-2.5 py-1.5",
        md: "h-10 px-3 py-2.5",
        lg: "h-12 px-4 py-3.5",
      },
    },
    compoundVariants: [
      {
        variant: "underline",
        size: ["sm", "md", "lg"],
        className: "px-0",
      },
    ],
    defaultVariants: {
      variant: "outlined",
      size: "sm",
    },
  },
);

export type SelectProps = React.ComponentProps<typeof SelectPrimitive.Root>;

const Select = SelectPrimitive.Root;

export type SelectGroupProps = React.ComponentProps<typeof SelectPrimitive.Group>;

function SelectGroup({
  className,
  ...props
}: SelectGroupProps) {
  return (
    <SelectPrimitive.Group
      data-slot="select-group"
      className={cn("flex scroll-my-1 flex-col gap-1", className)}
      {...props}
    />
  );
}

export type SelectValueProps = React.ComponentProps<typeof SelectPrimitive.Value>;

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

export type SelectTriggerProps = React.ComponentProps<
  typeof SelectPrimitive.Trigger
> &
  VariantProps<typeof selectTriggerVariants>;

function SelectTrigger({
  className,
  variant = "outlined",
  size = "sm",
  children,
  ...props
}: SelectTriggerProps) {
  return (
    <SelectPrimitive.Trigger
      data-slot="select-trigger"
      data-variant={variant}
      data-size={size}
      className={cn(selectTriggerVariants({ variant, size }), className)}
      {...props}
    >
      {children}
      <SelectPrimitive.Icon
        render={
          <ChevronDown className="pointer-events-none size-4 text-icon-muted" />
        }
      />
    </SelectPrimitive.Trigger>
  );
}

export type SelectContentProps = React.ComponentProps<typeof SelectPrimitive.Popup> &
  Pick<
    React.ComponentProps<typeof SelectPrimitive.Positioner>,
    "align" | "alignOffset" | "side" | "sideOffset"
  > & {
    position?: "item-aligned" | "popper";
  };

function SelectContent({
  className,
  children,
  position = "item-aligned",
  align = "center",
  ...props
}: SelectContentProps) {
  const alignItemWithTrigger = position === "item-aligned";
  return (
    <SelectPrimitive.Portal>
      <SelectPrimitive.Positioner
        align={align}
        alignItemWithTrigger={alignItemWithTrigger}
        className="isolate z-50"
      >
        <SelectPrimitive.Popup
          data-slot="select-content"
          data-align-trigger={alignItemWithTrigger}
          className={cn(
            "relative z-50 max-h-(--available-height) min-w-[max(--spacing(36),var(--anchor-width))] origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md border border-border-default bg-bg-main p-1 text-text-strong shadow-md duration-100 data-closed:overflow-hidden data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
            !alignItemWithTrigger &&
              "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
            className,
          )}
          {...props}
        >
          <SelectScrollUpButton />
          <SelectPrimitive.List className="flex flex-col gap-1">{children}</SelectPrimitive.List>
          <SelectScrollDownButton />
        </SelectPrimitive.Popup>
      </SelectPrimitive.Positioner>
    </SelectPrimitive.Portal>
  );
}

export type SelectLabelProps = React.ComponentProps<typeof SelectPrimitive.GroupLabel>;

function SelectLabel({
  className,
  ...props
}: SelectLabelProps) {
  return (
    <SelectPrimitive.GroupLabel
      data-slot="select-label"
      className={cn("px-2 py-1 text-xs text-text-muted", className)}
      {...props}
    />
  );
}

export type SelectItemProps = React.ComponentProps<typeof SelectPrimitive.Item>;

function SelectItem({
  className,
  children,
  ...props
}: SelectItemProps) {
  return (
    <SelectPrimitive.Item
      data-slot="select-item"
      className={cn(
        "relative flex w-full cursor-default items-center gap-2 rounded-md py-1.5 pr-10 pl-2 text-sm leading-[1.4] text-text-strong outline-hidden select-none data-selected:bg-bg-surface-1 data-disabled:pointer-events-none data-disabled:text-text-disabled [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-5",
        className,
      )}
      {...props}
    >
      <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center">
        <SelectPrimitive.ItemIndicator>
          <Check01 className="pointer-events-none text-icon-strong size-4" />
        </SelectPrimitive.ItemIndicator>
      </span>
      <SelectPrimitive.ItemText className="flex items-center gap-2">
        {children}
      </SelectPrimitive.ItemText>
    </SelectPrimitive.Item>
  );
}

export type SelectSeparatorProps = React.ComponentProps<typeof SelectPrimitive.Separator>;

function SelectSeparator({
  className,
  ...props
}: SelectSeparatorProps) {
  return (
    <SelectPrimitive.Separator
      data-slot="select-separator"
      className={cn(
        "pointer-events-none -mx-1 my-1 h-px bg-border-default",
        className,
      )}
      {...props}
    />
  );
}

export type SelectScrollUpButtonProps = React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>;

function SelectScrollUpButton({
  className,
  ...props
}: SelectScrollUpButtonProps) {
  return (
    <SelectPrimitive.ScrollUpArrow
      data-slot="select-scroll-up-button"
      className={cn(
        "top-0 z-10 flex w-full cursor-default items-center justify-center bg-bg-main py-1 text-icon-muted [&_svg:not([class*='size-'])]:size-3",
        className,
      )}
      {...props}
    >
      <ChevronUp />
    </SelectPrimitive.ScrollUpArrow>
  );
}

export type SelectScrollDownButtonProps = React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>;

function SelectScrollDownButton({
  className,
  ...props
}: SelectScrollDownButtonProps) {
  return (
    <SelectPrimitive.ScrollDownArrow
      data-slot="select-scroll-down-button"
      className={cn(
        "bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-bg-main py-1 text-icon-muted [&_svg:not([class*='size-'])]:size-3",
        className,
      )}
      {...props}
    >
      <ChevronDown />
    </SelectPrimitive.ScrollDownArrow>
  );
}

export {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectScrollDownButton,
  SelectScrollUpButton,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
  selectTriggerVariants,
};