TWIGS

Select

Displays a list of options for the user to pick from.

Playground

Installation

Install in any React + Tailwind project:

$npx shadcn@latest add https://twigs.globirdenergy.com.au/r/select.json

Source

The exact file the registry serves to consumers.

'use client';

import * as React from 'react';
import { Select as BaseSelect } from '@base-ui/react/select';
import { Check, ChevronDown, ChevronUp } from 'lucide-react';
import { cn } from '@/lib/utils';

const Select = BaseSelect.Root;
const SelectGroup = BaseSelect.Group;

function SelectValue({
  placeholder,
  className,
  ...props
}: React.ComponentProps<typeof BaseSelect.Value> & { placeholder?: React.ReactNode }) {
  return (
    <BaseSelect.Value
      data-slot="select-value"
      className={className}
      {...props}
    >
      {(value: unknown) => (value == null || value === '' ? placeholder : (value as React.ReactNode))}
    </BaseSelect.Value>
  );
}

function SelectTrigger({
  className,
  children,
  ...props
}: React.ComponentProps<typeof BaseSelect.Trigger>) {
  return (
    // Spec source: Figma "APP Re-design Phase 1" → page "Select ✅"
    <BaseSelect.Trigger
      data-slot="select-trigger"
      className={cn(
        "data-[panel-open]:border-ring focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 aria-invalid:border-destructive flex h-10 w-fit items-center justify-between gap-2 rounded-xl bg-secondary px-4 py-2 text-sm font-medium whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 *: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-4 [&_svg:not([class*='text-'])]:text-muted-foreground",
        className,
      )}
      {...props}
    >
      {children}
      <BaseSelect.Icon className="size-4 opacity-50">
        <ChevronDown className="size-4" />
      </BaseSelect.Icon>
    </BaseSelect.Trigger>
  );
}

function SelectContent({
  className,
  children,
  ...props
}: React.ComponentProps<typeof BaseSelect.Popup>) {
  return (
    <BaseSelect.Portal>
      <BaseSelect.Positioner sideOffset={4}>
        <BaseSelect.Popup
          data-slot="select-content"
          className={cn(
            'bg-popover text-popover-foreground z-50 max-h-[--available-height] min-w-[var(--anchor-width)] overflow-hidden rounded-xl border shadow-lg',
            'data-[ending-style]:opacity-0 data-[starting-style]:opacity-0 transition-opacity',
            className,
          )}
          {...props}
        >
          <BaseSelect.ScrollUpArrow className="flex h-6 cursor-default items-center justify-center">
            <ChevronUp className="size-4" />
          </BaseSelect.ScrollUpArrow>
          <BaseSelect.List className="p-1">{children}</BaseSelect.List>
          <BaseSelect.ScrollDownArrow className="flex h-6 cursor-default items-center justify-center">
            <ChevronDown className="size-4" />
          </BaseSelect.ScrollDownArrow>
        </BaseSelect.Popup>
      </BaseSelect.Positioner>
    </BaseSelect.Portal>
  );
}

function SelectItem({
  className,
  children,
  ...props
}: React.ComponentProps<typeof BaseSelect.Item>) {
  return (
    <BaseSelect.Item
      data-slot="select-item"
      className={cn(
        "data-[highlighted]:bg-secondary data-[highlighted]:text-foreground relative flex w-full cursor-default items-center gap-2 rounded-md py-2 pr-8 pl-3 text-sm outline-hidden select-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
        className,
      )}
      {...props}
    >
      <span className="absolute right-2 flex size-3.5 items-center justify-center">
        <BaseSelect.ItemIndicator>
          <Check className="size-4" />
        </BaseSelect.ItemIndicator>
      </span>
      <BaseSelect.ItemText>{children}</BaseSelect.ItemText>
    </BaseSelect.Item>
  );
}

export { Select, SelectGroup, SelectValue, SelectTrigger, SelectContent, SelectItem };