UIPackage
Menu

Framework

Change language

Boilerplate repo

Select

selectui

Dropdown select primitive — single-select with optional groups, per-item descriptions, Radix UI popover mode, and zero-JS styled NativeSelect for lightweight/mobile use.

Also available for Vue ->

When to use which

Decision Guide

Select is the headless primitive for flat options. Advance Select is the searchable combobox. Cascade and Tree selectors are designed specifically for hierarchical and nested datasets.

ComponentRole
Selectcurrent

Short, flat option lists (< 20 items) where typing to filter is unnecessary.

Primitive Dropdown
AdvanceSelect

Large datasets requiring fuzzy search, multi-select tag pills, or remote option filtering.

Searchable Combobox
CascadeSelect

Sequential multi-level drilldowns (e.g. Country > State > City, Category > Subcategory).

Hierarchical Picker
TreeSelect

Deeply nested taxonomy structures, folder trees, or parent-child permission nodes.

Tree Picker
  • Standard dropdown with static options → Select
  • Searchable or multi-select combobox → Advance Select (view)
  • Sequential multi-level drilldown → Cascade Select (view)
  • Nested tree structure with checkboxes → Tree Select (view)

Installation

$npx shadcn@latest add https://uipkge.dev/r/react/select.json
Named registry:npx shadcn@latest add @uipkge-react/selectInstalls to:components/ui/select/

Examples

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
size'sm' | 'default' | 'lg'defaultoptional
state'default' | 'error' | 'success'defaultoptional
loadingbooleanfalseoptional

Schema

Type aliases from this item's source — use them to shape the data you pass in.

NativeSelectOption
interface NativeSelectOption {
  label: string
  value: string | number
  disabled?: boolean
}

Used by

Files installed (3)

  • components/ui/select/select.tsx8.1 kB
    'use client'
    
    import * as React from 'react'
    import * as SelectPrimitive from '@radix-ui/react-select'
    import { Check, ChevronDown, ChevronUp, Loader } from 'lucide-react'
    import { cn } from '@/lib/utils'
    
    const Select = SelectPrimitive.Root
    
    const SelectGroup = SelectPrimitive.Group
    
    const SelectValue = React.forwardRef<
      React.ElementRef<typeof SelectPrimitive.Value>,
      React.ComponentPropsWithoutRef<typeof SelectPrimitive.Value>
    >((props, ref) => <SelectPrimitive.Value ref={ref} data-slot="select-value" {...props} />)
    SelectValue.displayName = SelectPrimitive.Value.displayName
    
    const triggerSizeClasses = {
      sm: 'h-8 text-sm px-2.5 py-1.5',
      default: 'h-9 text-sm px-3 py-2',
      lg: 'h-11 text-base px-4 py-2.5',
    }
    
    const triggerStateClasses = {
      default: 'border-input dark:hover:bg-input/50',
      error:
        'border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
      success: 'border-success focus-visible:border-success',
    }
    
    export interface SelectTriggerProps extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> {
      size?: 'sm' | 'default' | 'lg'
      state?: 'default' | 'error' | 'success'
      loading?: boolean
    }
    
    const SelectTrigger = React.forwardRef<React.ElementRef<typeof SelectPrimitive.Trigger>, SelectTriggerProps>(
      ({ className, children, size = 'default', state = 'default', loading = false, disabled, ...props }, ref) => (
        <SelectPrimitive.Trigger
          ref={ref}
          data-uipkge=""
          data-slot="select-trigger"
          data-size={size}
          data-state-value={state}
          disabled={disabled || loading}
          aria-busy={loading}
          className={cn(
            "border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex w-full items-center justify-between gap-2 rounded-md border bg-transparent text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
            triggerSizeClasses[size],
            triggerStateClasses[state],
            className,
          )}
          {...props}
        >
          {children}
          {loading ? (
            <Loader className="size-4 animate-spin opacity-50" aria-hidden="true" />
          ) : (
            <SelectPrimitive.Icon asChild>
              <ChevronDown className="size-4 opacity-50" aria-hidden="true" />
            </SelectPrimitive.Icon>
          )}
        </SelectPrimitive.Trigger>
      ),
    )
    SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
    
    const SelectScrollUpButton = React.forwardRef<
      React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
      React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
    >(({ className, children, ...props }, ref) => (
      <SelectPrimitive.ScrollUpButton
        ref={ref}
        data-uipkge=""
        data-slot="select-scroll-up-button"
        className={cn('flex cursor-default items-center justify-center py-1', className)}
        {...props}
      >
        {children ?? <ChevronUp className="size-4" aria-hidden="true" />}
      </SelectPrimitive.ScrollUpButton>
    ))
    SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
    
    const SelectScrollDownButton = React.forwardRef<
      React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
      React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
    >(({ className, children, ...props }, ref) => (
      <SelectPrimitive.ScrollDownButton
        ref={ref}
        data-uipkge=""
        data-slot="select-scroll-down-button"
        className={cn('flex cursor-default items-center justify-center py-1', className)}
        {...props}
      >
        {children ?? <ChevronDown className="size-4" aria-hidden="true" />}
      </SelectPrimitive.ScrollDownButton>
    ))
    SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
    
    const SelectContent = React.forwardRef<
      React.ElementRef<typeof SelectPrimitive.Content>,
      React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
    >(({ className, children, position = 'popper', ...props }, ref) => (
      <SelectPrimitive.Portal>
        <SelectPrimitive.Content
          ref={ref}
          data-uipkge=""
          data-slot="select-content"
          position={position}
          className={cn(
            'bg-popover text-popover-foreground motion-safe:data-[state=open]:animate-in motion-safe:data-[state=closed]:animate-out motion-safe:data-[state=open]:ease-emphasized motion-safe:data-[state=open]:blur-in-2 motion-safe:data-[state=closed]:blur-out-2 motion-safe:data-[state=closed]:fade-out-0 motion-safe:data-[state=open]:fade-in-0 motion-safe:data-[state=closed]:zoom-out-95 motion-safe:data-[state=open]:zoom-in-95 motion-safe:data-[side=bottom]:slide-in-from-top-2 motion-safe:data-[side=left]:slide-in-from-right-2 motion-safe:data-[side=right]:slide-in-from-left-2 motion-safe:data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] overflow-x-hidden overflow-y-auto rounded-md border shadow-md motion-safe:data-[state=closed]:duration-[var(--dur-exit)] motion-safe:data-[state=open]:duration-200',
            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,
          )}
          {...props}
        >
          <SelectScrollUpButton />
          <SelectPrimitive.Viewport
            className={cn(
              'p-1',
              position === 'popper' &&
                'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1',
            )}
          >
            {children}
          </SelectPrimitive.Viewport>
          <SelectScrollDownButton />
        </SelectPrimitive.Content>
      </SelectPrimitive.Portal>
    ))
    SelectContent.displayName = SelectPrimitive.Content.displayName
    
    const SelectLabel = React.forwardRef<
      React.ElementRef<typeof SelectPrimitive.Label>,
      React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
    >(({ className, ...props }, ref) => (
      <SelectPrimitive.Label
        ref={ref}
        data-uipkge=""
        data-slot="select-label"
        className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}
        {...props}
      />
    ))
    SelectLabel.displayName = SelectPrimitive.Label.displayName
    
    const SelectItem = React.forwardRef<
      React.ElementRef<typeof SelectPrimitive.Item>,
      React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
    >(({ className, children, ...props }, ref) => (
      <SelectPrimitive.Item
        ref={ref}
        data-uipkge=""
        data-slot="select-item"
        className={cn(
          "focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
          className,
        )}
        {...props}
      >
        <span className="absolute right-2 flex size-3.5 items-center justify-center">
          <SelectPrimitive.ItemIndicator>
            <Check className="size-4" aria-hidden="true" />
          </SelectPrimitive.ItemIndicator>
        </span>
    
        <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
      </SelectPrimitive.Item>
    ))
    SelectItem.displayName = SelectPrimitive.Item.displayName
    
    const SelectItemText = SelectPrimitive.ItemText
    
    const SelectSeparator = React.forwardRef<
      React.ElementRef<typeof SelectPrimitive.Separator>,
      React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
    >(({ className, ...props }, ref) => (
      <SelectPrimitive.Separator
        ref={ref}
        data-uipkge=""
        data-slot="select-separator"
        className={cn('bg-border pointer-events-none -mx-1 my-1 h-px', className)}
        {...props}
      />
    ))
    SelectSeparator.displayName = SelectPrimitive.Separator.displayName
    
    export {
      Select,
      SelectContent,
      SelectGroup,
      SelectItem,
      SelectItemText,
      SelectLabel,
      SelectScrollDownButton,
      SelectScrollUpButton,
      SelectSeparator,
      SelectTrigger,
      SelectValue,
    }
    
  • components/ui/select/NativeSelect.tsx2.5 kB
  • components/ui/select/index.ts0.3 kB

Raw manifest:https://uipkge.dev/r/react/select.json