For the complete documentation index, see llms.txt. Markdown variants are available by appending .md to any URL or sending an Accept: text/markdown header. An agent skill is available at /.well-known/agent-skills/site-skill.md.
Home

Select

A custom dropdown for selecting a single value — with groups, labels, separators, and keyboard navigation.

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

export const SelectDemo = () => (
    <div className="flex flex-col gap-4">
      {/* Basic */}
      <Select>
        <SelectTrigger className="w-56"><SelectValue placeholder="Select a fruit" /></SelectTrigger>
        <SelectContent>
          <SelectItem value="apple">Apple</SelectItem>
          <SelectItem value="banana">Banana</SelectItem>
          <SelectItem value="blueberry">Blueberry</SelectItem>
          <SelectItem value="grapes">Grapes</SelectItem>
          <SelectItem value="pineapple">Pineapple</SelectItem>
        </SelectContent>
      </Select>
      {/* Groups + separator */}
      <Select>
        <SelectTrigger className="w-56"><SelectValue placeholder="Select a timezone" /></SelectTrigger>
        <SelectContent>
          <SelectGroup>
            <SelectLabel>North America</SelectLabel>
            <SelectItem value="est">Eastern (EST)</SelectItem>
            <SelectItem value="cst">Central (CST)</SelectItem>
            <SelectItem value="mst">Mountain (MST)</SelectItem>
            <SelectItem value="pst">Pacific (PST)</SelectItem>
          </SelectGroup>
          <SelectSeparator />
          <SelectGroup>
            <SelectLabel>Europe</SelectLabel>
            <SelectItem value="gmt">Greenwich (GMT)</SelectItem>
            <SelectItem value="cet">Central European (CET)</SelectItem>
            <SelectItem value="wet">Western European (WET)</SelectItem>
          </SelectGroup>
        </SelectContent>
      </Select>
      {/* Small size */}
      <Select>
        <SelectTrigger size="sm" className="w-40"><SelectValue placeholder="Sort by" /></SelectTrigger>
        <SelectContent>
          <SelectItem value="newest">Newest</SelectItem>
          <SelectItem value="oldest">Oldest</SelectItem>
          <SelectItem value="az">A → Z</SelectItem>
          <SelectItem value="za">Z → A</SelectItem>
        </SelectContent>
      </Select>
      {/* Disabled */}
      <Select disabled>
        <SelectTrigger className="w-56"><SelectValue placeholder="Disabled" /></SelectTrigger>
        <SelectContent><SelectItem value="x">Option</SelectItem></SelectContent>
      </Select>
    </div>
  );

With groups

Use SelectGroup + SelectLabel to organise long option lists into named sections. Add SelectSeparator between groups.

<SelectContent>
  <SelectGroup>
    <SelectLabel>North America</SelectLabel>
    <SelectItem value="est">Eastern (EST)</SelectItem>
    <SelectItem value="pst">Pacific (PST)</SelectItem>
  </SelectGroup>
  <SelectSeparator />
  <SelectGroup>
    <SelectLabel>Europe</SelectLabel>
    <SelectItem value="gmt">Greenwich (GMT)</SelectItem>
  </SelectGroup>
</SelectContent>

Small size

<SelectTrigger size="sm" className="w-40">
  <SelectValue placeholder="Sort by" />
</SelectTrigger>

Controlled

const [value, setValue] = useState("");
 
<Select value={value} onValueChange={setValue}>
  <SelectTrigger className="w-56">
    <SelectValue placeholder="Select…" />
  </SelectTrigger>
  <SelectContent>
    <SelectItem value="option-a">Option A</SelectItem>
    <SelectItem value="option-b">Option B</SelectItem>
  </SelectContent>
</Select>

Disabled item

<SelectItem value="enterprise" disabled>
  Enterprise (coming soon)
</SelectItem>

With Field

import { Field, FieldError, FieldLabel, useField } from "@/components/ui/field";
 
function FieldSelect(props: React.ComponentProps<typeof Select>) {
  const { id, error } = useField();
  return (
    <Select {...props}>
      <SelectTrigger id={id} aria-invalid={!!error} className="w-full">
        <SelectValue placeholder="Select a role" />
      </SelectTrigger>
      <SelectContent>
        <SelectItem value="admin">Admin</SelectItem>
        <SelectItem value="editor">Editor</SelectItem>
        <SelectItem value="viewer">Viewer</SelectItem>
      </SelectContent>
    </Select>
  );
}
 
<Field error="Please select a role.">
  <FieldLabel>Role</FieldLabel>
  <FieldSelect />
  <FieldError />
</Field>

Select vs NativeSelect

SelectNativeSelect
RenderingCustom Radix popoverOS-native <select>
Custom option stylingYesLimited
SearchableVia Command (Combobox)No
Mobile UXCustom overlayFull native
Form submissionNeeds hidden inputNative

Props

SelectTrigger

PropTypeDefaultDescription
size"default" | "sm""default"h-9 or h-8
aria-invalidboolean—Applies destructive ring and border
disabledbooleanfalsePassed via Select root

SelectContent

PropTypeDefaultDescription
position"popper" | "item-aligned""popper"popper anchors below trigger; item-aligned aligns to selected item

SelectItem

PropTypeDefaultDescription
valuestring—The value this item represents (required)
disabledbooleanfalseDisables this specific option

Usage

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
 
<Select>
  <SelectTrigger className="w-56">
    <SelectValue placeholder="Select an option" />
  </SelectTrigger>
  <SelectContent>
    <SelectItem value="option-a">Option A</SelectItem>
    <SelectItem value="option-b">Option B</SelectItem>
    <SelectItem value="option-c">Option C</SelectItem>
  </SelectContent>
</Select>

Specs

# Mint — Select Specs
 
## Component
- name: select
- file: registry/mint/select.tsx
- installs to: components/ui/select.tsx
- dependencies: radix-ui, lucide-react
- registryDependencies: none
 
## What is it
A fully accessible custom dropdown for single-value selection, built on Radix
Select. Renders in a Portal with animated open/close transitions and supports
groups, labels, and separators for long option lists. Prefer Select over
NativeSelect when custom option styling or keyboard-first UX matters; use
NativeSelect for mobile-first forms where OS-native behaviour is preferred.
 
## Visual identity
Clean, minimal trigger — transparent background with a subtle border and
rounded corners (0.625 rem). The popover surface uses --popover with a soft
shadow. Selected state is marked by a CheckIcon rather than background
highlight, keeping the list visually light.
 
## Design tokens
--input: oklch(0.88 0.04 155 / 60%) / oklch(0.32 0.05 155 / 40%)          — trigger border (border-input)
--ring: oklch(0.5 0.14 155) / oklch(0.62 0.15 155)              — focus ring (focus-visible:ring-ring/50)
--muted-foreground: oklch(0.52 0.06 155) / oklch(0.6 0.06 155)  — placeholder text, chevron icon, label text
--destructive: oklch(0.55 0.25 15) / oklch(0.704 0.191 22.216) — aria-invalid border and ring
--popover: oklch(1 0 0) / oklch(0.269 0 0)               — content panel background
--popover-foreground: oklch(0.145 0 0) / oklch(0.985 0 0) — content panel text
--accent: oklch(0.92 0.05 155) / oklch(0.26 0.05 155)             — focused item background (focus:bg-accent)
--accent-foreground: oklch(0.32 0.1 155) / oklch(0.88 0.06 155) — focused item text
--border: oklch(0.88 0.04 155 / 60%) / oklch(0.32 0.05 155 / 40%)          — content border, separator (bg-border)
 
## Sub-components
- Select                   → Root wrapper; sets data-slot="select", no visual output
- SelectGroup              → Groups items; sets data-slot="select-group", no visual output
- SelectValue              → Placeholder/value text slot; sets data-slot="select-value"
- SelectTrigger            → Interactive trigger button; h-9 (default) or h-8 (sm); ChevronDown icon appended automatically
- SelectContent            → Portal-rendered popover panel; z-50, max-h-96, animated open/close
- SelectItem               → Individual option row; CheckIcon indicator on the right when selected
- SelectLabel              → Section heading within a group; text-xs, muted foreground
- SelectSeparator          → 1px horizontal rule between groups; -mx-1 my-1
- SelectScrollUpButton     → Scroll affordance shown at top of long lists; ChevronUp icon
- SelectScrollDownButton   → Scroll affordance shown at bottom of long lists; ChevronDown icon
 
## States
- focus-visible:  3px ring in --ring/50, border switches to --ring — keyboard focus on trigger
- hover (dark):   trigger background shifts to --input/50 (dark:hover:bg-input/50)
- disabled:       cursor-not-allowed, opacity-50 on trigger; data-disabled items get pointer-events-none opacity-50
- aria-invalid:   trigger border becomes --destructive; ring becomes --destructive/20 (light) or --destructive/40 (dark)
- data-[state=open] / data-[state=closed]: fade + zoom animation on SelectContent
- data-[side=*]:  slide-in direction matches popover placement (bottom/top/left/right)
- focus (item):   bg-accent + text-accent-foreground on the focused SelectItem
 
## Variants
- size="default"  → h-9 trigger — standard form fields
- size="sm"       → h-8 trigger — compact toolbars, dense layouts
 
## Conventions
- Always wrap SelectTrigger + SelectContent inside a Select root
- SelectValue must be a direct child of SelectTrigger; set placeholder for empty state
- For groups use SelectGroup + SelectLabel; add SelectSeparator between groups
- Pair with Field + FieldLabel + FieldError from the field component for form validation — pass aria-invalid={!!error} to SelectTrigger
- SelectContent position="popper" (default) anchors below the trigger with a 1px gap; use "item-aligned" to align to the selected item
- SelectItem value prop is required; duplicate values within one Select are not allowed
- Sub-components expose data-slot attributes for CSS targeting: "select", "select-group", "select-value", "select-trigger", "select-content", "select-item", "select-label", "select-separator", "select-scroll-up-button", "select-scroll-down-button"
- Keyboard: Space/Enter opens and selects; ArrowUp/Down navigates; typing triggers first-match navigation; Escape closes

Installation

GIN Design Plugin
$ npx shadcn add https://gin-design.vercel.app/select