Select

A select displays a collapsible list of options and allows a user to select one of them.


New Project

Installation

Install via VScode or copy and paste the code below into a new file, preferably in a folder at components/base.

BaseLayer

import type {
  SelectProps as AriaSelectProps,
  ListBoxItemProps,
} from "react-aria-components";

import {
  Select as AriaSelect,
  Button,
  ListBoxItem,
  Label,
  ListBox,
  Popover,
  SelectValue,
  Text,
} from "react-aria-components";

import { ChevronDown } from "lucide-react";
import { tv } from "tailwind-variants";

const select = tv({
  slots: {
    root: "max-h-inherit flex flex-col gap-2 overflow-auto p-1 outline-none",
    item: "relative m-1 flex cursor-default flex-col rounded-md p-2 outline-none ring-focus ring-offset-surface aria-selected:bg-primary data-[focus-visible]:border-transparent data-[focus-visible]:bg-surface data-[hovered]:bg-surface-2 data-[focus-visible]:ring-2 data-[hovered]:aria-selected:bg-primary data-[focus-visible]:aria-selected:ring-offset-2",
    popover:
      "w-64 rounded-xl border border-border bg-surface p-2 text-fg shadow-xl outline-none",
    button:
      "flex w-56 appearance-none items-center justify-between rounded-md border border-border p-2 outline-none ring-focus ring-offset-2 ring-offset-surface data-[hovered]:bg-surface-2 data-[focus-visible]:ring-2",
  },
});

const { button, item, popover, root } = select();

interface SelectProps<T extends object>
  extends Omit<AriaSelectProps<T>, "children"> {
  className?: string;
  label?: string;
  description?: string | null;
  errorMessage?: string | null;
  children: React.ReactNode | ((item: T) => React.ReactNode);
}

const Select = <T extends object>({
  label,
  className,
  description,
  errorMessage,
  children,
  ...props
}: SelectProps<T>) => (
  <AriaSelect className={root({ className })} {...props}>
    <Label>{label}</Label>
    <Button className={button()}>
      <SelectValue />
      <ChevronDown />
    </Button>
    {description && <Text slot="description">{description}</Text>}
    {errorMessage && <Text slot="errorMessage">{errorMessage}</Text>}
    <Popover className={popover()}>
      <ListBox className="outline-none">{children}</ListBox>
    </Popover>
  </AriaSelect>
);

const SelectItem = (props: ListBoxItemProps) => {
  return <ListBoxItem {...props} className={item()} />;
};

export { Select, SelectItem };

Examples

Default