Kosorikōsori alpha

Select

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

Installation

Install the primitive

Install the @radix-ui/react-select package.

npm install @radix-ui/react-select

Copy-paste the component

Copy and paste the component code in a .tsx file.

'use client';
 
import { forwardRef } from 'react';
import {
  CaretSortIcon,
  CheckIcon,
  ChevronDownIcon,
  ChevronUpIcon,
} from '@radix-ui/react-icons';
import {
  Content,
  Group,
  Icon,
  Item,
  ItemIndicator,
  ItemText,
  Label,
  Portal,
  Root,
  ScrollDownButton,
  ScrollUpButton,
  Separator,
  Trigger,
  Value,
  Viewport,
} from '@radix-ui/react-select';
import { clsx } from 'clsx/lite';
import { tv } from 'tailwind-variants';
 
const selectStyles = tv({
  slots: {
    trigger: clsx(
      'flex h-9 w-full cursor-pointer items-center justify-between whitespace-nowrap rounded-lg border border-grey-border bg-grey-base px-3 py-2 text-sm shadow-sm outline-none transition-colors duration-200',
      'placeholder:text-grey-placeholder-text',
      'focus:ring-4 focus:ring-grey-focus-ring',
      'hover:border-grey-border-hover',
      'disabled:cursor-not-allowed disabled:bg-grey-bg disabled:text-grey-solid',
      '[&>span]:line-clamp-1',
    ),
    triggerIcon: 'size-4 fill-grey-text',
    content: clsx(
      'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-lg border border-grey-line bg-grey-base shadow-md',
      'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
      'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
      'data-[side=bottom]:slide-in-from-top-2',
      'data-[side=left]:slide-in-from-right-2',
      'data-[side=right]:slide-in-from-left-2',
      'data-[side=top]:slide-in-from-bottom-2',
    ),
    viewport: 'p-1',
    item: clsx(
      'relative flex w-full cursor-pointer select-none items-center rounded-md py-1.5 pl-8 pr-2 text-sm outline-none transition-colors duration-200',
      'focus:bg-primary-bg-hover',
      'data-[disabled]:cursor-not-allowed data-[disabled]:text-grey-solid',
    ),
    itemIndicator: 'absolute left-2 flex items-center justify-center',
    itemIcon: 'size-4',
    scrollUpButton: 'flex cursor-default items-center justify-center py-1',
    scrollDownButton: 'flex cursor-default items-center justify-center py-1',
    label:
      'flex select-none items-center py-1.5 pl-8 pr-2 text-xs font-medium text-grey-text',
    separator: '-mx-1 my-1 h-px bg-grey-line',
  },
  variants: {
    position: {
      popper: {
        content: clsx(
          'data-[side=bottom]:translate-y-1',
          'data-[side=left]:-translate-x-1',
          'data-[side=right]:translate-x-1',
          'data-[side=top]:-translate-y-1',
        ),
        viewport:
          'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]',
      },
      'item-aligned': '',
    },
  },
});
 
const {
  trigger,
  triggerIcon,
  content,
  viewport,
  item,
  itemIndicator,
  itemIcon,
  scrollUpButton,
  scrollDownButton,
  label,
  separator,
} = selectStyles();
 
/**
 * Select component that serves as a container for selectable items.
 *
 * @param {React.ComponentPropsWithoutRef<typeof Root>} props - The props for the Select component.
 *
 * @example
 * <Select>
 *   <SelectTrigger className='w-[180px]'>
 *     <SelectValue placeholder='Theme' />
 *   </SelectTrigger>
 *   <SelectContent>
 *     <SelectItem value='light'>Light</SelectItem>
 *     <SelectItem value='dark'>Dark</SelectItem>
 *     <SelectItem value='system'>System</SelectItem>
 *   </SelectContent>
 * </Select>
 *
 * @see {@link https://dub.sh/ui-select Select Docs} for further information.
 */
export const Select = Root;
 
type SelectTriggerRef = React.ElementRef<typeof Trigger>;
type SelectTriggerProps = React.ComponentPropsWithoutRef<typeof Trigger>;
 
/**
 * SelectTrigger component that triggers the display of the select options.
 *
 * @param {SelectTriggerProps} props - The props for the SelectTrigger component.
 *
 * @example
 * <SelectTrigger className='w-[180px]'>Select an option</SelectTrigger>
 */
export const SelectTrigger = forwardRef<SelectTriggerRef, SelectTriggerProps>(
  ({ className, children, ...props }, ref) => (
    <Trigger ref={ref} className={trigger({ className })} {...props}>
      {children}
 
      <Icon asChild>
        <CaretSortIcon className={triggerIcon()} />
      </Icon>
    </Trigger>
  ),
);
 
SelectTrigger.displayName = Trigger.displayName;
 
/**
 * SelectValue component that displays the currently selected value.
 *
 * @param {React.ComponentPropsWithoutRef<typeof Value>} props - The props for the SelectValue component.
 *
 * @example
 * <SelectValue placeholder='Select an option' />
 */
export const SelectValue = Value;
 
type SelectContentRef = React.ElementRef<typeof Content>;
type SelectContentProps = React.ComponentPropsWithoutRef<typeof Content>;
 
/**
 * SelectContent component that displays the selectable options.
 *
 * @param {SelectContentProps} props - The props for the SelectContent component.
 *
 * @example
 * <SelectContent>
 *   <SelectItem value='light'>Light</SelectItem>
 *   <SelectItem value='dark'>Dark</SelectItem>
 * </SelectContent>
 */
export const SelectContent = forwardRef<SelectContentRef, SelectContentProps>(
  ({ className, children, position, ...props }, ref) => (
    <Portal>
      <Content
        ref={ref}
        className={content({ className, position })}
        position={position}
        {...props}
      >
        <SelectScrollUpButton />
        <Viewport className={viewport({ position })}>{children}</Viewport>
        <SelectScrollDownButton />
      </Content>
    </Portal>
  ),
);
 
SelectContent.displayName = Content.displayName;
 
type SelectItemRef = React.ElementRef<typeof Item>;
type SelectItemProps = React.ComponentPropsWithoutRef<typeof Item>;
 
/**
 * SelectItem component that represents an individual option in the select.
 *
 * @param {SelectItemProps} props - The props for the SelectItem component.
 *
 * @example
 * <SelectItem value='light'>Light</SelectItem>
 */
export const SelectItem = forwardRef<SelectItemRef, SelectItemProps>(
  ({ className, children, ...props }, ref) => (
    <Item ref={ref} className={item({ className })} {...props}>
      <span className={itemIndicator()}>
        <ItemIndicator>
          <CheckIcon className={itemIcon()} />
        </ItemIndicator>
      </span>
 
      <ItemText>{children}</ItemText>
    </Item>
  ),
);
 
SelectItem.displayName = Item.displayName;
 
type SelectScrollUpButtonRef = React.ElementRef<typeof ScrollUpButton>;
type SelectScrollUpButtonProps = React.ComponentPropsWithoutRef<
  typeof ScrollUpButton
>;
 
/**
 * SelectScrollUpButton component that allows scrolling up in the select options.
 *
 * @param {SelectScrollUpButtonProps} props - The props for the SelectScrollUpButton component.
 *
 * @example
 * <SelectScrollUpButton />
 */
export const SelectScrollUpButton = forwardRef<
  SelectScrollUpButtonRef,
  SelectScrollUpButtonProps
>(({ className, ...props }, ref) => (
  <ScrollUpButton
    ref={ref}
    className={scrollUpButton({ className })}
    {...props}
  >
    <ChevronUpIcon />
  </ScrollUpButton>
));
 
SelectScrollUpButton.displayName = ScrollUpButton.displayName;
 
type SelectScrollDownButtonRef = React.ElementRef<typeof ScrollDownButton>;
type SelectScrollDownButtonProps = React.ComponentPropsWithoutRef<
  typeof ScrollDownButton
>;
 
/**
 * SelectScrollDownButton component that allows scrolling down in the select options.
 *
 * @param {SelectScrollDownButtonProps} props - The props for the SelectScrollDownButton component.
 *
 * @example
 * <SelectScrollDownButton />
 */
export const SelectScrollDownButton = forwardRef<
  SelectScrollDownButtonRef,
  SelectScrollDownButtonProps
>(({ className, ...props }, ref) => (
  <ScrollDownButton
    ref={ref}
    className={scrollDownButton({ className })}
    {...props}
  >
    <ChevronDownIcon />
  </ScrollDownButton>
));
 
SelectScrollDownButton.displayName = ScrollDownButton.displayName;
 
/**
 * SelectGroup component that groups related select items.
 *
 * @param {React.ComponentPropsWithoutRef<typeof Group>} props - The props for the SelectGroup component.
 *
 * @example
 * <SelectGroup>
 *   <SelectItem value='light'>Light</SelectItem>
 * </SelectGroup>
 */
export const SelectGroup = Group;
 
type SelectLabelRef = React.ElementRef<typeof Label>;
type SelectLabelProps = React.ComponentPropsWithoutRef<typeof Label>;
 
/**
 * SelectLabel component that provides a label for the select group.
 *
 * @param {SelectLabelProps} props - The props for the SelectLabel component.
 *
 * @example
 * <SelectLabel>Choose a theme</SelectLabel>
 */
export const SelectLabel = forwardRef<SelectLabelRef, SelectLabelProps>(
  ({ className, ...props }, ref) => (
    <Label ref={ref} className={label({ className })} {...props} />
  ),
);
 
SelectLabel.displayName = Label.displayName;
 
type SelectSeparatorRef = React.ElementRef<typeof Separator>;
type SelectSeparatorProps = React.ComponentPropsWithoutRef<typeof Separator>;
 
/**
 * SelectSeparator component that visually separates items in the select.
 *
 * @param {SelectSeparatorProps} props - The props for the SelectSeparator component.
 *
 * @example
 * <SelectSeparator />
 */
export const SelectSeparator = forwardRef<
  SelectSeparatorRef,
  SelectSeparatorProps
>(({ className, ...props }, ref) => (
  <Separator ref={ref} className={separator({ className })} {...props} />
));
 
SelectSeparator.displayName = Separator.displayName;

Update import paths

Update the @kosori/ui import paths to fit your project structure, for example, using ~/components/ui.

Usage

import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '~/components/ui/select';
<Select>
  <SelectTrigger className='w-[180px]'>
    <SelectValue placeholder='Theme' />
  </SelectTrigger>
  <SelectContent>
    <SelectItem value='light'>Light</SelectItem>
    <SelectItem value='dark'>Dark</SelectItem>
    <SelectItem value='system'>System</SelectItem>
  </SelectContent>
</Select>

Examples

Scrollable

Form

You can manage email addresses in your email settings.

On this page