Select

A select component for choosing a value from a list of options. Features keyboard navigation, grouped options, custom positioning, and full accessibility support.

Usage

Basic Select

A simple select with options:

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@prisma/eclipse";

export function BasicSelect() {
  return (
    <Select>
      <SelectTrigger>
        <SelectValue placeholder="Select a fruit" />
      </SelectTrigger>
      <SelectContent>
        <SelectItem value="apple">Apple</SelectItem>
        <SelectItem value="banana">Banana</SelectItem>
        <SelectItem value="orange">Orange</SelectItem>
        <SelectItem value="grape">Grape</SelectItem>
      </SelectContent>
    </Select>
  );
}

Live Example:

Live Example:

Grouped Options

Organize options into labeled groups:

import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
} from "@prisma/eclipse";

export function GroupedSelect() {
  return (
    <Select>
      <SelectTrigger>
        <SelectValue placeholder="Select a language" className="capitalize" />
      </SelectTrigger>
      <SelectContent>
        <SelectGroup>
          <SelectLabel>Frontend</SelectLabel>
          <SelectItem value="javascript">JavaScript</SelectItem>
          <SelectItem value="typescript">TypeScript</SelectItem>
        </SelectGroup>
        <SelectSeparator />
        <SelectGroup>
          <SelectLabel>Backend</SelectLabel>
          <SelectItem value="python">Python</SelectItem>
          <SelectItem value="rust">Rust</SelectItem>
          <SelectItem value="go">Go</SelectItem>
        </SelectGroup>
      </SelectContent>
    </Select>
  );
}

Live Example:

Live Example:

Small Size

Use the sm size for compact layouts:

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@prisma/eclipse";

export function SmallSelect() {
  return (
    <Select>
      <SelectTrigger size="sm">
        <SelectValue placeholder="Select size" className="capitalize" />
      </SelectTrigger>
      <SelectContent>
        <SelectItem value="xs">Extra Small</SelectItem>
        <SelectItem value="sm">Small</SelectItem>
        <SelectItem value="md">Medium</SelectItem>
        <SelectItem value="lg">Large</SelectItem>
      </SelectContent>
    </Select>
  );
}

Live Example:

Live Example:

Disabled State

Disable the select when interaction should be prevented:

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@prisma/eclipse";

export function DisabledSelect() {
  return (
    <Select disabled>
      <SelectTrigger>
        <SelectValue placeholder="Disabled select" className="capitalize" />
      </SelectTrigger>
      <SelectContent>
        <SelectItem value="option1">Option 1</SelectItem>
        <SelectItem value="option2">Option 2</SelectItem>
      </SelectContent>
    </Select>
  );
}

Live Example:

Live Example:

With Field Component

Use with Field for proper form structure:

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
  Field,
  FieldLabel,
  FieldDescription,
} from "@prisma/eclipse";

export function SelectWithField() {
  return (
    <Field>
      <FieldLabel htmlFor="country">Country</FieldLabel>
      <Select>
        <SelectTrigger id="country">
          <SelectValue placeholder="Select your country" className="capitalize" />
        </SelectTrigger>
        <SelectContent>
          <SelectItem value="us">United States</SelectItem>
          <SelectItem value="uk">United Kingdom</SelectItem>
          <SelectItem value="ca">Canada</SelectItem>
          <SelectItem value="au">Australia</SelectItem>
        </SelectContent>
      </Select>
      <FieldDescription>Choose your country of residence.</FieldDescription>
    </Field>
  );
}

Live Example:

Live Example:

Choose your country of residence.

Position Control

Control the position and alignment of the dropdown:

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@prisma/eclipse";

export function PositionedSelect() {
  return (
    <Select>
      <SelectTrigger>
        <SelectValue placeholder="Select position" className="capitalize" />
      </SelectTrigger>
      <SelectContent side="top" align="end">
        <SelectItem value="top">Top</SelectItem>
        <SelectItem value="bottom">Bottom</SelectItem>
        <SelectItem value="left">Left</SelectItem>
        <SelectItem value="right">Right</SelectItem>
      </SelectContent>
    </Select>
  );
}

Live Example:

Live Example:

Disabled Items

Disable individual items:

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@prisma/eclipse";

export function SelectWithDisabledItems() {
  return (
    <Select>
      <SelectTrigger>
        <SelectValue placeholder="Select an option" className="capitalize" />
      </SelectTrigger>
      <SelectContent>
        <SelectItem value="option1">Available Option</SelectItem>
        <SelectItem value="option2" disabled>
          Disabled Option
        </SelectItem>
        <SelectItem value="option3">Another Available</SelectItem>
        <SelectItem value="option4" disabled>
          Also Disabled
        </SelectItem>
      </SelectContent>
    </Select>
  );
}

Live Example:

Live Example:

API Reference

Select

The root component that manages select state.

Props:

  • value - Controlled value (string, optional)
  • onValueChange - Callback when value changes ((value: string) => void, optional)
  • open - Controlled open state (boolean, optional)
  • onOpenChange - Callback when open state changes ((open: boolean) => void, optional)
  • disabled - Disable the select (boolean, default: false)
  • defaultValue - Default value for uncontrolled mode (string, optional)
  • defaultOpen - Default open state (boolean, default: false)
  • name - Form input name (string, optional)
  • required - Mark as required (boolean, default: false)
<Select value={value} onValueChange={setValue}>
  {/* SelectTrigger and SelectContent */}
</Select>

SelectTrigger

The button that triggers the select dropdown.

Props:

  • size - Size variant ("sm" | "default", default: "default")
  • className - Additional CSS classes (string or function, optional)
  • disabled - Disable the trigger (boolean, default: false)
  • All standard HTML button attributes
<SelectTrigger size="sm">
  <SelectValue placeholder="Choose..." />
</SelectTrigger>

SelectValue

Displays the currently selected value or placeholder.

Props:

  • placeholder - Text to show when no value is selected (string, optional)
  • className - Additional CSS classes (string or function, optional)
<SelectValue placeholder="Select an option" />

SelectContent

The dropdown container that holds the list of options.

Props:

  • side - Position relative to trigger ("top" | "bottom" | "left" | "right", default: "bottom")
  • align - Alignment relative to trigger ("start" | "center" | "end", default: "center")
  • sideOffset - Distance from trigger in pixels (number, default: 4)
  • alignOffset - Alignment offset in pixels (number, default: 0)
  • alignItemWithTrigger - Align selected item with trigger (boolean, default: true)
  • className - Additional CSS classes (string or function, optional)
<SelectContent side="top" align="end" sideOffset={8}>
  {/* SelectItem components */}
</SelectContent>

SelectItem

An individual option in the select list.

Props:

  • value - The value of the item (string, required)
  • disabled - Disable the item (boolean, default: false)
  • className - Additional CSS classes (string or function, optional)
  • children - Item content (ReactNode)
<SelectItem value="option1">Option 1</SelectItem>
<SelectItem value="option2" disabled>Disabled Option</SelectItem>

SelectGroup

A container for grouping related items.

Props:

  • className - Additional CSS classes (string or function, optional)
  • children - Group content (ReactNode)
<SelectGroup>
  <SelectLabel>Category</SelectLabel>
  <SelectItem value="item1">Item 1</SelectItem>
</SelectGroup>

SelectLabel

A label for a group of items.

Props:

  • className - Additional CSS classes (string or function, optional)
  • children - Label content (ReactNode)
<SelectLabel>Frontend Languages</SelectLabel>

SelectSeparator

A visual separator between groups or items.

Props:

  • className - Additional CSS classes (string or function, optional)
<SelectSeparator />

SelectScrollUpButton

Internal component for scrolling up in long lists. Automatically rendered.

SelectScrollDownButton

Internal component for scrolling down in long lists. Automatically rendered.

Features

  • ✅ Two size variants (default and small)
  • ✅ Keyboard navigation (Arrow keys, Enter, Escape, Tab, Home, End)
  • ✅ Grouped options with labels
  • ✅ Custom positioning and alignment
  • ✅ Disabled state for select and individual items
  • ✅ Controlled and uncontrolled modes
  • ✅ Scroll buttons for long lists
  • ✅ Item indicator with checkmark
  • ✅ Accessible with ARIA attributes
  • ✅ Portal rendering for z-index management
  • ✅ Animations for open/close transitions
  • ✅ Auto-alignment of selected item with trigger
  • ✅ Form integration with name and required attributes
  • ✅ Fully typed with TypeScript

Best Practices

  • Always provide a placeholder for better UX
  • Use SelectGroup and SelectLabel for organized lists
  • Consider using the small size in dense layouts
  • Disable items that are temporarily unavailable rather than hiding them
  • Keep option text concise and scannable
  • Use Field component for proper form structure with labels
  • Test keyboard navigation thoroughly
  • Avoid extremely long lists (consider search/filter for 50+ items)
  • Use consistent option formatting
  • Provide clear value descriptions

Accessibility

The Select component follows ARIA combobox pattern specifications:

  • Uses semantic ARIA roles (combobox, listbox, option)
  • Supports full keyboard navigation:
    • Space/Enter - Open/close and select
    • Arrow Down/Up - Navigate through options
    • Home/End - Jump to first/last option
    • Escape - Close the dropdown
    • Tab - Move focus and close dropdown
  • Focus management and trapped focus in dropdown
  • Screen reader announcements for selections and state changes
  • Proper labeling with aria-label and aria-labelledby
  • Visual focus indicators
  • Selected state announced to assistive technologies
  • Disabled items are properly marked and unfocusable
  • Required attribute for form validation

Common Use Cases

The Select component is perfect for:

  • Form inputs - Collecting user choices in forms
  • Settings - Configuration and preference selection
  • Filters - Filtering data by category
  • Language/locale switchers - Selecting language or region
  • Status updates - Changing item status
  • Priority selection - Setting task or issue priority
  • Category selection - Choosing from predefined categories
  • Time zones - Selecting time zone
  • Country/region pickers - Location selection
  • Sorting options - Choosing sort order
  • Role assignment - Selecting user roles

Styling

The Select component uses design tokens and can be customized:

  • Trigger: Border, rounded corners, padding, hover/focus states
  • Content: Popover with shadow, rounded corners, animations
  • Items: Hover/focus states with accent colors
  • Selected indicator: Checkmark icon aligned right
  • Groups: Scroll margin and padding
  • Labels: Muted text, small font size
  • Separator: Border line between groups
  • Scroll buttons: Positioned at top/bottom with icons

Customize by passing className props:

<Select>
  <SelectTrigger className="w-full border-2">
    <SelectValue placeholder="Custom styled" />
  </SelectTrigger>
  <SelectContent className="max-h-[200px]">
    <SelectItem className="font-bold" value="custom">
      Custom Item
    </SelectItem>
  </SelectContent>
</Select>

Integration with Forms

Use with form libraries like React Hook Form:

import { useForm, Controller } from "react-hook-form";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@prisma/eclipse";

export function SelectForm() {
  const { control, handleSubmit } = useForm();

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <Controller
        name="country"
        control={control}
        render={({ field }) => (
          <Select value={field.value} onValueChange={field.onChange}>
            <SelectTrigger>
              <SelectValue placeholder="Select country" />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="us">United States</SelectItem>
              <SelectItem value="uk">United Kingdom</SelectItem>
              <SelectItem value="ca">Canada</SelectItem>
            </SelectContent>
          </Select>
        )}
      />
      <button type="submit">Submit</button>
    </form>
  );
}

Controlled vs Uncontrolled

Uncontrolled (with defaultValue):

<Select defaultValue="option1">
  <SelectTrigger>
    <SelectValue />
  </SelectTrigger>
  <SelectContent>
    <SelectItem value="option1">Option 1</SelectItem>
    <SelectItem value="option2">Option 2</SelectItem>
  </SelectContent>
</Select>

Controlled (with value and onValueChange):

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

<Select value={value} onValueChange={setValue}>
  <SelectTrigger>
    <SelectValue />
  </SelectTrigger>
  <SelectContent>
    <SelectItem value="option1">Option 1</SelectItem>
    <SelectItem value="option2">Option 2</SelectItem>
  </SelectContent>
</Select>

Performance Tips

For large option lists:

  1. Virtualization - Consider using virtual scrolling for 100+ items
  2. Lazy loading - Load options on-demand
  3. Memoization - Use React.memo for SelectItem components
  4. Search/filter - Add search for easier navigation
  5. Pagination - Load options in batches
import { useMemo } from "react";

export function OptimizedSelect({ items }) {
  const memoizedItems = useMemo(
    () => items.map((item) => (
      <SelectItem key={item.id} value={item.id}>
        {item.name}
      </SelectItem>
    )),
    [items]
  );

  return (
    <Select>
      <SelectTrigger>
        <SelectValue placeholder="Select..." />
      </SelectTrigger>
      <SelectContent>
        {memoizedItems}
      </SelectContent>
    </Select>
  );
}

On this page