Light

Select

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

Spec · from metadata

When to use

  • Choosing a single value from a predefined list of 4+ options
  • Form fields where the user must pick from fixed options
  • Dropdown filters in toolbars or table headers

When not to use

  • Fewer than 4 options — use RadioGroup for visibility
  • Multiple selections — use Checkbox group or Combobox
  • Searchable/filterable list — use Combobox
  • Free-text input with suggestions — use Combobox or Autocomplete

Variants

PropValuesDefaultDescription
size (SelectTrigger)defaultsmdefaultControls trigger height: default (h-9) or sm (h-8).

Anti-patterns

Avoid<Select onChange={handleChange}>...</Select>
Prefer<Select onValueChange={handleChange}>...</Select>

Radix Select uses onValueChange, not onChange.

Avoid<select className="border rounded p-2">
  <option value="a">A</option>
</select>
Prefer<Select>
  <SelectTrigger>
    <SelectValue placeholder="Choose..." />
  </SelectTrigger>
  <SelectContent>
    <SelectItem value="a">A</SelectItem>
  </SelectContent>
</Select>

Use the Select component for consistent styling, keyboard navigation, and accessibility.

Avoid<FormControl>
  <Select onValueChange={field.onChange}>
    <SelectTrigger>...</SelectTrigger>
    ...
  </Select>
</FormControl>
Prefer<Select onValueChange={field.onChange}>
  <FormControl>
    <SelectTrigger>...</SelectTrigger>
  </FormControl>
  <SelectContent>...</SelectContent>
</Select>

FormControl must wrap SelectTrigger directly, not the Select root. Select is a context provider, not a DOM element.

Accessibility

  • Required ARIAaria-expanded (auto)aria-invalid (via FormControl)
  • Screen readerRadix Select provides full ARIA listbox semantics. Selected item is announced. Scroll buttons are decorative.
  • ContrastFocus items use bg-accent/text-accent-foreground. Disabled items have reduced opacity.

Token bindings

TokenCategoryUsage
bg-input-bgcolorTrigger background
border-inputcolorTrigger border
bg-popovercolorDropdown content background
text-popover-foregroundcolorDropdown text color
bg-accentcolorFocused item background
rounded-mdradiusTrigger and content border radius
shadow-mdshadowDropdown content elevation

Import

import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectTrigger,
  SelectValue,
} from "@timelycare/helix-ui"

Props

interface SelectProps {
  value?: string
  defaultValue?: string
  onValueChange?: (value: string) => void
  disabled?: boolean
}

interface SelectTriggerProps {
  className?: string
  children: React.ReactNode
}

interface SelectItemProps {
  value: string
  disabled?: boolean
  className?: string
  children: React.ReactNode
}

Structure

PartTailwind
SelectTriggerh-9 px-3 py-2 gap-2 bg-input-bg border border-input rounded-md shadow-xs
Placeholdertext-sm text-muted-foreground
Selected valuetext-sm text-foreground
Chevron iconsize-4 text-muted-foreground
SelectContentbg-popover border border-border rounded-md shadow-md p-1 — uses position="popper" (default)
SelectItempl-2 pr-8 py-1.5 gap-2 rounded-sm text-sm text-popover-foreground
SelectItem hoverbg-accent text-accent-foreground
SelectLabelpx-2 py-1.5 text-xs font-semibold text-muted-foreground
Check iconsize-4 absolute right-2

States

StateTrigger Appearance
Defaultborder-input bg-input-bg placeholder visible
Focusring-2 ring-ring ring-offset-2
FilledSelected value in text-foreground
Filled (Focus)Ring + selected value
Disabledopacity-50 cursor-not-allowed

Positioning

SelectContent uses popper positioning (position="popper"), which is the default. The dropdown menu appears directly below (or above, if space is limited) the trigger, aligned to the trigger edge. It never overlaps the trigger.

Do not use position="item-aligned", which aligns the popup to the selected item and overlaps the trigger. Popper positioning keeps the trigger visible while the menu is open and provides a clearer visual relationship between the trigger and the option list.

The dropdown width matches or exceeds the trigger width via min-w-[var(--radix-select-trigger-width)].


Item Variants

VariantUse ForImplementation
DefaultStandard selectionText only, check on selected
With IconVisual identificationIcon (size-2.5) + text + check
CheckboxMulti-appearanceCircle indicator + text + check

Common Patterns

Basic Select

<Select>
  <SelectTrigger className="w-[200px]">
    <SelectValue placeholder="Select option" />
  </SelectTrigger>
  <SelectContent>
    <SelectItem value="option1">Option 1</SelectItem>
    <SelectItem value="option2">Option 2</SelectItem>
    <SelectItem value="option3">Option 3</SelectItem>
  </SelectContent>
</Select>

With Label and Description

<div className="space-y-2">
  <Label htmlFor="framework" className="font-medium text-sm">
    Label
  </Label>
  <Select>
    <SelectTrigger id="framework" className="w-[200px]">
      <SelectValue placeholder="Placeholder" />
    </SelectTrigger>
    <SelectContent>
      <SelectItem value="next">Next.js</SelectItem>
      <SelectItem value="remix">Remix</SelectItem>
    </SelectContent>
  </Select>
  <p className="text-sm text-muted-foreground">
    This is a select description.
  </p>
</div>

With Groups

<Select>
  <SelectTrigger className="w-[200px]">
    <SelectValue placeholder="Select fruit" />
  </SelectTrigger>
  <SelectContent>
    <SelectGroup>
      <SelectLabel>Fruits</SelectLabel>
      <SelectItem value="apple">Apple</SelectItem>
      <SelectItem value="banana">Banana</SelectItem>
    </SelectGroup>
    <SelectGroup>
      <SelectLabel>Vegetables</SelectLabel>
      <SelectItem value="carrot">Carrot</SelectItem>
    </SelectGroup>
  </SelectContent>
</Select>

Scrollable List

<Select>
  <SelectTrigger className="w-[200px]">
    <SelectValue placeholder="Large list" />
  </SelectTrigger>
  <SelectContent className="max-h-[200px]">
    {items.map((item) => (
      <SelectItem key={item.value} value={item.value}>
        {item.label}
      </SelectItem>
    ))}
  </SelectContent>
</Select>

Accessibility

  • Built on Radix Select — handles keyboard and focus automatically
  • Keyboard: Enter/Space to open, Arrow keys to navigate, Enter to select, Escape to close
  • Screen reader: announces selected value and available options
  • Typeahead search supported
  • Requires label association via Label component

Gotchas

ProblemSolution
Placeholder not showingUse <SelectValue placeholder="..." />
Check icon misalignedItem has pr-8 to reserve space
Width inconsistentSet explicit width: w-[200px] or w-full
Scroll not showingAdd max-h-[200px] to SelectContent

See Also


Last updated: February 27, 2026