Aura Design System

Select

A select component that allows users to select a value from a list of options.

Preview

Loading...
import { useState } from "react";
import {
  Select,
  SelectTrigger,
  SelectValue,
  SelectIcon,
  SelectPortal,
  SelectContent,
  SelectViewport,
  SelectItem,
  SelectItemText,
  SelectItemIndicator,
  SelectGroup,
  SelectLabel,
  SelectSeparator,
  SelectScrollUpButton,
  SelectScrollDownButton,
  SelectArrow,
} from "@/components/ui/Select";
import { Label } from "@/components/ui/Label";

export function SelectDemo() {
  return {
  const [value, setValue] = useState("apple");

  return (
    <div className="space-y-1">
      <Label>Choose a fruit</Label>
      <Select value={value} onValueChange={setValue}>
        <SelectTrigger className="w-40">
          <SelectValue placeholder="Select a fruit..." />
          <SelectIcon />
        </SelectTrigger>
        <SelectPortal>
          <SelectContent>
            <SelectScrollUpButton />
            <SelectViewport>
              <SelectItem value="apple">
                <SelectItemText>Apple</SelectItemText>
                <SelectItemIndicator />
              </SelectItem>
              <SelectItem value="banana">
                <SelectItemText>Banana</SelectItemText>
                <SelectItemIndicator />
              </SelectItem>
              <SelectItem value="orange">
                <SelectItemText>Orange</SelectItemText>
                <SelectItemIndicator />
              </SelectItem>
              <SelectItem value="grape">
                <SelectItemText>Grape</SelectItemText>
                <SelectItemIndicator />
              </SelectItem>
              <SelectItem value="mango">
                <SelectItemText>Mango</SelectItemText>
                <SelectItemIndicator />
              </SelectItem>
            </SelectViewport>
            <SelectScrollDownButton />
            <SelectArrow />
          </SelectContent>
        </SelectPortal>
      </Select>
    </div>
  );
};

Installation

Make sure that namespace is set in your component.json file. Namespace docs: Learn more about namespaces

pnpm dlx shadcn@latest add @aura/select

Manual

Install the following dependencies:

pnpm install @radix-ui/react-icons radix-ui

Copy and paste the class names utility into your utils/class-names.ts file.

import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

Copy and paste the Select component into your components/ui/Select.tsx file.

"use client";
/**
 * @description Displays a list of options for the user to pick from—triggered by a button.
 */
import * as React from "react";
import { Select as SelectRadix } from "radix-ui";
import {
  CheckIcon,
  ChevronDownIcon,
  ChevronUpIcon,
} from "@radix-ui/react-icons";

import { cn } from "@/utils/class-names";

function Select({ ...props }: React.ComponentProps<typeof SelectRadix.Root>) {
  return <SelectRadix.Root data-slot="select" {...props} />;
}

function SelectTrigger({
  className,
  ...props
}: React.ComponentProps<typeof SelectRadix.Trigger>) {
  return (
    <SelectRadix.Trigger
      data-slot="select-trigger"
      className={cn(
        "flex h-4 w-full items-center justify-between rounded-sm border border-gray-a6 bg-gray-2 px-2 text-gray-12 cursor-pointer focus:outline-2 focus:outline-accent-9 focus:outline-offset-0 disabled:cursor-not-allowed disabled:opacity-50",
        className
      )}
      {...props}
    />
  );
}

function SelectValue({
  className,
  ...props
}: React.ComponentProps<typeof SelectRadix.Value>) {
  return (
    <SelectRadix.Value
      data-slot="select-value"
      className={cn(className, "text-gray-12")}
      {...props}
    />
  );
}

function SelectIcon({
  className,
  ...props
}: React.ComponentProps<typeof SelectRadix.Icon>) {
  return (
    <SelectRadix.Icon
      data-slot="select-icon"
      className={cn(className, "text-gray-11")}
      {...props}
    >
      <ChevronDownIcon className="icon" />
    </SelectRadix.Icon>
  );
}

function SelectPortal({
  ...props
}: React.ComponentProps<typeof SelectRadix.Portal>) {
  return <SelectRadix.Portal data-slot="select-portal" {...props} />;
}

function SelectContent({
  className,
  ...props
}: React.ComponentProps<typeof SelectRadix.Content>) {
  return (
    <SelectRadix.Content
      data-slot="select-content"
      collisionPadding={8}
      className={cn(
        className,
        "relative z-50 max-h-96 min-w-10 overflow-hidden rounded-sm border border-gray-a6 bg-gray-1 shadow-md"
      )}
      {...props}
    />
  );
}

function SelectViewport({
  className,
  ...props
}: React.ComponentProps<typeof SelectRadix.Viewport>) {
  return (
    <SelectRadix.Viewport
      data-slot="select-viewport"
      className={cn(className, "p-1")}
      {...props}
    />
  );
}

function SelectItem({
  className,
  children,
  ...props
}: React.ComponentProps<typeof SelectRadix.Item>) {
  return (
    <SelectRadix.Item
      data-slot="select-item"
      className={cn(
        className,
        "relative flex w-full cursor-pointer select-none items-center rounded-sm py-1 px-2 text-gray-12 outline-none focus:bg-accent-3 focus:text-accent-12 data-[disabled]:pointer-events-none data-[disabled]:opacity-50"
      )}
      {...props}
    >
      {children}
    </SelectRadix.Item>
  );
}

function SelectItemText({
  className,
  ...props
}: React.ComponentProps<typeof SelectRadix.ItemText>) {
  return (
    <SelectRadix.ItemText
      data-slot="select-item-text"
      className={cn(className)}
      {...props}
    />
  );
}

function SelectItemIndicator({
  className,
  ...props
}: React.ComponentProps<typeof SelectRadix.ItemIndicator>) {
  return (
    <SelectRadix.ItemIndicator
      data-slot="select-item-indicator"
      className={cn(
        className,
        "absolute left-0.5 top-0 bottom-0 flex items-center justify-center"
      )}
      {...props}
    >
      <CheckIcon className="size-1 text-accent-9" />
    </SelectRadix.ItemIndicator>
  );
}

function SelectGroup({
  ...props
}: React.ComponentProps<typeof SelectRadix.Group>) {
  return <SelectRadix.Group data-slot="select-group" {...props} />;
}

function SelectLabel({
  className,
  ...props
}: React.ComponentProps<typeof SelectRadix.Label>) {
  return (
    <SelectRadix.Label
      data-slot="select-label"
      className={cn(className, "py-1 px-2 text-gray-12 font-medium")}
      {...props}
    />
  );
}

function SelectSeparator({
  className,
  ...props
}: React.ComponentProps<typeof SelectRadix.Separator>) {
  return (
    <SelectRadix.Separator
      data-slot="select-separator"
      className={cn(className, "m-0.6 h-px bg-gray-a6")}
      {...props}
    />
  );
}

function SelectScrollUpButton({
  className,
  ...props
}: React.ComponentProps<typeof SelectRadix.ScrollUpButton>) {
  return (
    <SelectRadix.ScrollUpButton
      data-slot="select-scroll-up-button"
      className={cn(
        className,
        "flex cursor-default items-center justify-center py-1"
      )}
      {...props}
    >
      <ChevronUpIcon className="size-1 text-gray-11" />
    </SelectRadix.ScrollUpButton>
  );
}

function SelectScrollDownButton({
  className,
  ...props
}: React.ComponentProps<typeof SelectRadix.ScrollDownButton>) {
  return (
    <SelectRadix.ScrollDownButton
      data-slot="select-scroll-down-button"
      className={cn(
        className,
        "flex cursor-default items-center justify-center py-1"
      )}
      {...props}
    >
      <ChevronDownIcon className="icon text-gray-11" />
    </SelectRadix.ScrollDownButton>
  );
}

function SelectArrow({
  className,
  ...props
}: React.ComponentProps<typeof SelectRadix.Arrow>) {
  return (
    <SelectRadix.Arrow
      data-slot="select-arrow"
      className={cn(className, "fill-gray-1")}
      {...props}
    />
  );
}

export {
  Select,
  SelectPortal,
  SelectTrigger,
  SelectValue,
  SelectIcon,
  SelectContent,
  SelectViewport,
  SelectItem,
  SelectItemText,
  SelectItemIndicator,
  SelectGroup,
  SelectLabel,
  SelectSeparator,
  SelectScrollUpButton,
  SelectScrollDownButton,
  SelectArrow,
};

Usage

WithDefaultValue

export const WithDefaultValue = () => {
  return (
    <div className="space-y-1">
      <Label>Choose a fruit</Label>
      <Select defaultValue="banana">
        <SelectTrigger className="w-40">
          <SelectValue placeholder="Select a fruit..." />
          <SelectIcon />
        </SelectTrigger>
        <SelectPortal>
          <SelectContent>
            <SelectViewport>
              <SelectItem value="apple">
                <SelectItemText>Apple</SelectItemText>
                <SelectItemIndicator />
              </SelectItem>
              <SelectItem value="banana">
                <SelectItemText>Banana</SelectItemText>
                <SelectItemIndicator />
              </SelectItem>
              <SelectItem value="orange">
                <SelectItemText>Orange</SelectItemText>
                <SelectItemIndicator />
              </SelectItem>
              <SelectItem value="grape">
                <SelectItemText>Grape</SelectItemText>
                <SelectItemIndicator />
              </SelectItem>
            </SelectViewport>
          </SelectContent>
        </SelectPortal>
      </Select>
    </div>
  );
};

WithGroups

export const WithGroups = () => {
  const [value, setValue] = useState("us");

  return (
    <div className="space-y-1">
      <Label>Choose a country</Label>
      <Select value={value} onValueChange={setValue}>
        <SelectTrigger className="w-40">
          <SelectValue placeholder="Select a country..." />
          <SelectIcon />
        </SelectTrigger>
        <SelectPortal>
          <SelectContent>
            <SelectViewport>
              <SelectGroup>
                <SelectLabel>Americas</SelectLabel>
                <SelectItem value="us">
                  <SelectItemText>United States</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
                <SelectItem value="ca">
                  <SelectItemText>Canada</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
                <SelectItem value="mx">
                  <SelectItemText>Mexico</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
              </SelectGroup>
              <SelectSeparator />
              <SelectGroup>
                <SelectLabel>Europe</SelectLabel>
                <SelectItem value="uk">
                  <SelectItemText>United Kingdom</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
                <SelectItem value="fr">
                  <SelectItemText>France</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
                <SelectItem value="de">
                  <SelectItemText>Germany</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
              </SelectGroup>
              <SelectSeparator />
              <SelectGroup>
                <SelectLabel>Asia</SelectLabel>
                <SelectItem value="jp">
                  <SelectItemText>Japan</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
                <SelectItem value="cn">
                  <SelectItemText>China</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
                <SelectItem value="kr">
                  <SelectItemText>South Korea</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
              </SelectGroup>
            </SelectViewport>
          </SelectContent>
        </SelectPortal>
      </Select>
    </div>
  );
};

WithDisabledItems

export const WithDisabledItems = () => {
  const [value, setValue] = useState("apple");

  return (
    <div className="space-y-1">
      <Label>Choose a fruit</Label>
      <Select value={value} onValueChange={setValue}>
        <SelectTrigger className="w-40">
          <SelectValue placeholder="Select a fruit..." />
          <SelectIcon />
        </SelectTrigger>
        <SelectPortal>
          <SelectContent>
            <SelectViewport>
              <SelectItem value="apple">
                <SelectItemText>Apple</SelectItemText>
                <SelectItemIndicator />
              </SelectItem>
              <SelectItem value="banana" disabled>
                <SelectItemText>Banana (disabled)</SelectItemText>
                <SelectItemIndicator />
              </SelectItem>
              <SelectItem value="orange">
                <SelectItemText>Orange</SelectItemText>
                <SelectItemIndicator />
              </SelectItem>
              <SelectItem value="grape" disabled>
                <SelectItemText>Grape (disabled)</SelectItemText>
                <SelectItemIndicator />
              </SelectItem>
              <SelectItem value="mango">
                <SelectItemText>Mango</SelectItemText>
                <SelectItemIndicator />
              </SelectItem>
            </SelectViewport>
          </SelectContent>
        </SelectPortal>
      </Select>
    </div>
  );
};

Disabled

export const Disabled = () => {
  return (
    <div className="space-y-1">
      <Label>Choose a fruit (disabled)</Label>
      <Select disabled>
        <SelectTrigger className="w-40">
          <SelectValue placeholder="Select a fruit..." />
          <SelectIcon />
        </SelectTrigger>
        <SelectPortal>
          <SelectContent>
            <SelectViewport>
              <SelectItem value="apple">
                <SelectItemText>Apple</SelectItemText>
                <SelectItemIndicator />
              </SelectItem>
              <SelectItem value="banana">
                <SelectItemText>Banana</SelectItemText>
                <SelectItemIndicator />
              </SelectItem>
              <SelectItem value="orange">
                <SelectItemText>Orange</SelectItemText>
                <SelectItemIndicator />
              </SelectItem>
            </SelectViewport>
          </SelectContent>
        </SelectPortal>
      </Select>
    </div>
  );
};

WithLongList

export const WithLongList = () => {
  const [value, setValue] = useState("apple");

  const fruits = [
    "Apple",
    "Banana",
    "Orange",
    "Pineapple",
    "Grape",
    "Mango",
    "Strawberry",
    "Blueberry",
    "Raspberry",
    "Blackberry",
    "Cherry",
    "Peach",
    "Pear",
    "Plum",
    "Kiwi",
    "Watermelon",
    "Cantaloupe",
    "Honeydew",
    "Papaya",
    "Guava",
  ];

  return (
    <div className="space-y-1">
      <Label>Choose a fruit</Label>
      <Select value={value} onValueChange={setValue}>
        <SelectTrigger className="w-40">
          <SelectValue placeholder="Select a fruit..." />
          <SelectIcon />
        </SelectTrigger>
        <SelectPortal>
          <SelectContent>
            <SelectScrollUpButton />
            <SelectViewport>
              {fruits.map((fruit) => (
                <SelectItem key={fruit} value={fruit.toLowerCase()}>
                  <SelectItemText>{fruit}</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
              ))}
            </SelectViewport>
            <SelectScrollDownButton />
            <SelectArrow />
          </SelectContent>
        </SelectPortal>
      </Select>
    </div>
  );
};

Complex

export const Complex = () => {
  const [value, setValue] = useState("us");

  return (
    <div className="space-y-1">
      <Label>Choose a location</Label>
      <Select value={value} onValueChange={setValue}>
        <SelectTrigger className="w-48">
          <SelectValue placeholder="Select a location..." />
          <SelectIcon />
        </SelectTrigger>
        <SelectPortal>
          <SelectContent>
            <SelectScrollUpButton />
            <SelectViewport>
              <SelectGroup>
                <SelectLabel>North America</SelectLabel>
                <SelectItem value="us">
                  <SelectItemText>United States</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
                <SelectItem value="ca">
                  <SelectItemText>Canada</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
                <SelectItem value="mx">
                  <SelectItemText>Mexico</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
              </SelectGroup>
              <SelectSeparator />
              <SelectGroup>
                <SelectLabel>Europe</SelectLabel>
                <SelectItem value="uk">
                  <SelectItemText>United Kingdom</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
                <SelectItem value="fr">
                  <SelectItemText>France</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
                <SelectItem value="de">
                  <SelectItemText>Germany</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
                <SelectItem value="it">
                  <SelectItemText>Italy</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
                <SelectItem value="es" disabled>
                  <SelectItemText>Spain (disabled)</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
              </SelectGroup>
              <SelectSeparator />
              <SelectGroup>
                <SelectLabel>Asia Pacific</SelectLabel>
                <SelectItem value="jp">
                  <SelectItemText>Japan</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
                <SelectItem value="cn">
                  <SelectItemText>China</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
                <SelectItem value="kr">
                  <SelectItemText>South Korea</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
                <SelectItem value="in">
                  <SelectItemText>India</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
                <SelectItem value="au">
                  <SelectItemText>Australia</SelectItemText>
                  <SelectItemIndicator />
                </SelectItem>
              </SelectGroup>
            </SelectViewport>
            <SelectScrollDownButton />
            <SelectArrow />
          </SelectContent>
        </SelectPortal>
      </Select>
    </div>
  );
};