For the complete documentation index, see llms.txt. Markdown variants are available by appending .md to documentation URLs. Homepage markdown is available at /llms.md/content.md. An agent skill is available at /.well-known/agent-skills/site-skill.md.

Select

The Select component from this design system, built on Base UI.

Installation

$ pnpm dlx shadcn@latest add @cobalt/select

Options

  • SelectTrigger variant="ghost" has no border, fill or shadow until hovered, for pickers that sit inline in a card or toolbar.

Source

"use client";

import { Select as SelectPrimitive } from "@base-ui/react/select";
import {
  RiArrowDownSLine,
  RiCheckLine,
  RiArrowUpSLine,
} from "@remixicon/react";
import { cn } from "cn";
import * as React from "react";

const Select = SelectPrimitive.Root;

function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
  return (
    <SelectPrimitive.Group
      data-slot="select-group"
      className={cn("scroll-my-1 flex flex-col gap-1", className)}
      {...props}
    />
  );
}

function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
  return (
    <SelectPrimitive.Value
      data-slot="select-value"
      className={cn(
        "flex min-w-0 flex-1 items-center gap-[5px] truncate text-left",
        className
      )}
      {...props}
    />
  );
}

function SelectTrigger({
  className,
  size = "default",
  variant = "default",
  children,
  label,
  id,
  ...props
}: SelectPrimitive.Trigger.Props & {
  size?: "xs" | "sm" | "default";
  /** `ghost` has no border, fill or shadow until hovered, for inline pickers. */
  variant?: "default" | "ghost";
  label?: React.ReactNode;
}) {
  const generatedId = React.useId();
  const triggerId = id ?? (label != null ? generatedId : undefined);
  const trigger = (
    <SelectPrimitive.Trigger
      id={triggerId}
      data-slot="select-trigger"
      data-size={size}
      className={cn(
        "flex w-fit cursor-pointer items-center justify-between gap-1.5 rounded-[10px] border border-border bg-popover px-2.5 text-sm leading-5 font-medium whitespace-nowrap text-foreground shadow-[shadow:var(--shadow-control,0_1px_2px_0_rgb(0_0_0/0.05))] outline-none transition-[background-color,border-color,box-shadow,padding,font-size] duration-200 ease-[ease] hover:border-[var(--border-hover,var(--border))] hover:bg-[var(--surface-hover,var(--muted))] focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:bg-muted disabled:text-[color:var(--foreground-subtle,var(--muted-foreground))] disabled:shadow-none aria-invalid:border-destructive data-[size=default]:h-9 data-[size=xs]:h-6 data-[size=xs]:gap-1 data-[size=xs]:rounded-[4px] data-[size=xs]:font-semibold data-[size=xs]:px-1.5 data-[size=xs]:text-xs data-[size=xs]:leading-4 data-[size=sm]:h-8 data-[size=sm]:gap-1 data-[size=sm]:px-[7px] data-[size=sm]:text-[13px] data-[size=sm]:leading-[18px] motion-reduce:transition-none [&_svg]:pointer-events-none [&_svg]:shrink-0",
        variant === "ghost" &&
          "border-transparent bg-transparent shadow-none hover:border-transparent",
        className
      )}
      data-variant={variant}
      {...props}
    >
      {children}
      <SelectPrimitive.Icon
        render={
          <RiArrowDownSLine className="pointer-events-none size-5 text-muted-foreground in-data-[size=xs]:size-4 transition-transform duration-200 ease-[ease] data-popup-open:rotate-180 motion-reduce:transition-none" />
        }
      />
    </SelectPrimitive.Trigger>
  );

  return label != null ? (
    <div className="flex w-fit flex-col gap-1">
      <label
        htmlFor={triggerId}
        className="cursor-default text-sm leading-5 font-medium text-foreground"
      >
        {label}
      </label>
      {trigger}
    </div>
  ) : (
    trigger
  );
}

function SelectContent({
  className,
  children,
  side = "bottom",
  sideOffset = 4,
  align = "start",
  alignOffset = 0,
  alignItemWithTrigger = false,
  ...props
}: SelectPrimitive.Popup.Props &
  Pick<
    SelectPrimitive.Positioner.Props,
    "align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger"
  >) {
  return (
    <SelectPrimitive.Portal>
      <SelectPrimitive.Positioner
        side={side}
        sideOffset={sideOffset}
        align={align}
        alignOffset={alignOffset}
        alignItemWithTrigger={alignItemWithTrigger}
        className="isolate z-50"
      >
        <SelectPrimitive.Popup
          data-slot="select-content"
          data-align-trigger={alignItemWithTrigger}
          className={cn(
            "group/select-content relative max-h-(--available-height) w-[266px] max-w-[calc(100vw-32px)] min-w-(--anchor-width) origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-[16px] border border-[var(--border-subtle,var(--border))] bg-[var(--surface-raised,var(--popover))] p-2 text-popover-foreground shadow-[shadow:var(--shadow-dropdown,0_1px_1px_0_rgb(0_0_0/0.04),0_4px_4px_0_rgb(0_0_0/0.02))] transition-[opacity,scale,filter] duration-150 ease-out data-starting-style:scale-95 data-starting-style:opacity-0 data-starting-style:blur-[2px] data-ending-style:scale-95 data-ending-style:opacity-0 data-ending-style:blur-[2px] motion-reduce:transition-none motion-reduce:data-starting-style:scale-100 motion-reduce:data-starting-style:opacity-100 motion-reduce:data-starting-style:blur-none motion-reduce:data-ending-style:scale-100 motion-reduce:data-ending-style:opacity-100 motion-reduce:data-ending-style:blur-none",
            className
          )}
          {...props}
        >
          <SelectScrollUpButton />
          <SelectPrimitive.List className="flex max-h-[240px] w-full flex-col gap-1 overflow-auto outline-none">
            {children}
          </SelectPrimitive.List>
          <SelectScrollDownButton />
        </SelectPrimitive.Popup>
      </SelectPrimitive.Positioner>
    </SelectPrimitive.Portal>
  );
}

function SelectLabel({
  className,
  ...props
}: SelectPrimitive.GroupLabel.Props) {
  return (
    <SelectPrimitive.GroupLabel
      data-slot="select-label"
      className={cn("px-2 py-1.5 text-xs text-muted-foreground", className)}
      {...props}
    />
  );
}

function SelectItem({
  className,
  children,
  indicator = false,
  ...props
}: SelectPrimitive.Item.Props & {
  /** Show a check mark at the end of the selected row. Off by default: the selected row is shown by its fill. */
  indicator?: boolean;
}) {
  return (
    <SelectPrimitive.Item
      data-slot="select-item"
      className={cn(
        "relative flex w-full cursor-pointer items-center gap-2 rounded-[10px] p-2 text-start text-sm leading-5 font-medium text-foreground outline-none select-none transition-colors group-data-ending-style/select-content:delay-150 isolate before:absolute before:inset-0 before:-z-10 before:rounded-[inherit] data-[selected]:transition-none data-[selected]:not-data-highlighted:before:bg-[var(--surface-hover,var(--accent))] data-highlighted:bg-[var(--surface-hover,var(--accent))] data-disabled:cursor-not-allowed data-disabled:text-[color:var(--foreground-disabled,var(--muted-foreground))] motion-reduce:transition-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className
      )}
      {...props}
    >
      <SelectPrimitive.ItemText className="flex min-w-0 flex-1 gap-2">
        {children}
      </SelectPrimitive.ItemText>
      {indicator ? (
        <SelectPrimitive.ItemIndicator
          render={
            <span className="pointer-events-none flex size-4 items-center justify-center" />
          }
        >
          <RiCheckLine className="pointer-events-none" />
        </SelectPrimitive.ItemIndicator>
      ) : null}
    </SelectPrimitive.Item>
  );
}

function SelectSeparator({
  className,
  ...props
}: SelectPrimitive.Separator.Props) {
  return (
    <SelectPrimitive.Separator
      data-slot="select-separator"
      className={cn("pointer-events-none my-1 h-px bg-border", className)}
      {...props}
    />
  );
}

function SelectScrollUpButton({
  className,
  ...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {
  return (
    <SelectPrimitive.ScrollUpArrow
      data-slot="select-scroll-up-button"
      className={cn(
        "top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
        className
      )}
      {...props}
    >
      <RiArrowUpSLine />
    </SelectPrimitive.ScrollUpArrow>
  );
}

function SelectScrollDownButton({
  className,
  ...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {
  return (
    <SelectPrimitive.ScrollDownArrow
      data-slot="select-scroll-down-button"
      className={cn(
        "bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
        className
      )}
      {...props}
    >
      <RiArrowDownSLine />
    </SelectPrimitive.ScrollDownArrow>
  );
}

export {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectScrollDownButton,
  SelectScrollUpButton,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
};