{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "select",
  "type": "registry:ui",
  "title": "Select",
  "description": "A drop-down list built on Base UI's Select and Field. The label, help text and error message are connected to the control, and a hidden input carries the chosen value when a form is submitted.",
  "categories": [
    "atom"
  ],
  "dependencies": [
    "@base-ui/react@^1.9.0",
    "lucide-react@^1.47.0"
  ],
  "registryDependencies": [
    "https://facadeui.dev/r/utils.json"
  ],
  "files": [
    {
      "path": "src/ui/select.tsx",
      "type": "registry:ui",
      "target": "components/ui/select.tsx",
      "content": "/**\n * Select — a drop-down list built on Base UI's Select inside a Field, so the\n * label, description and error are wired to the control the same way `Input`\n * wires them.\n *\n * a11y notes worth keeping:\n *\n *  - The trigger is a `combobox` button named by the label. Base UI handles the\n *    listbox roles, arrow keys, typeahead, Escape and returning focus.\n *  - `Field.Root invalid` marks the trigger `aria-invalid` and turns the border\n *    red for an error passed in from outside, such as one from a server.\n *  - \"required\" is spelled out in the label, as in `Input`.\n *  - A hidden `<input>` carries the value, so the field works in a plain form.\n *  - Options are 44px tall. The popup sits below the trigger rather than over\n *    it, so the label stays visible while the list is open.\n *\n * Not a client component: it creates no functions and holds no state. The\n * placeholder is a `null` entry in Base UI's `items`, because a function child\n * on `Select.Value` could not be rendered from a server component.\n *\n * Dependencies: @base-ui/react, lucide-react, react,\n * @/lib/utils.\n */\n\nimport { Field } from \"@base-ui/react/field\"\nimport { Select as BaseSelect } from \"@base-ui/react/select\"\nimport { CheckIcon, ChevronDownIcon } from \"lucide-react\"\nimport type { ReactNode } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface SelectOption {\n  value: string\n  label: string\n  disabled?: boolean\n}\n\nexport interface SelectProps {\n  /** Required. Use `hideLabel` if it should not be visible. */\n  label: ReactNode\n  /** Hides the label visually but keeps it for screen readers. */\n  hideLabel?: boolean\n  /** Help text below the field. It is linked to the field for you. */\n  description?: ReactNode\n  /** Error message. Marks the field invalid and is announced with it. */\n  error?: ReactNode\n  /** The options, in order. */\n  items: SelectOption[]\n  /** Shown in the trigger until something is chosen. */\n  placeholder?: string\n  /** Names the value in a submitted form. */\n  name?: string\n  /** The chosen value. Use with `onValueChange`. */\n  value?: string | null\n  /** The value on first render, when you do not control it. */\n  defaultValue?: string | null\n  /** Called with the new value when the user chooses an option. */\n  onValueChange?: (value: string | null) => void\n  required?: boolean\n  disabled?: boolean\n  id?: string\n  /** Classes for the outer Field.Root. */\n  className?: string\n  /** Classes for the trigger button. */\n  triggerClassName?: string\n}\n\nexport function Select({\n  label,\n  hideLabel = false,\n  description,\n  error,\n  items,\n  placeholder = \"Choose one\",\n  name,\n  value,\n  defaultValue,\n  onValueChange,\n  required,\n  disabled,\n  id,\n  className,\n  triggerClassName,\n}: SelectProps) {\n  // Base UI shows the label of the `null` item while nothing is chosen.\n  const labels = [{ value: null, label: placeholder }, ...items]\n\n  return (\n    <Field.Root\n      name={name}\n      invalid={Boolean(error)}\n      className={cn(\"flex w-full flex-col gap-1.5\", className)}\n    >\n      <Field.Label\n        className={cn(\"text-foreground text-sm font-medium\", hideLabel && \"sr-only\")}\n      >\n        {label}\n        {required ? <span className=\"text-muted-foreground\"> (required)</span> : null}\n      </Field.Label>\n\n      <BaseSelect.Root\n        items={labels}\n        name={name}\n        value={value}\n        defaultValue={defaultValue}\n        onValueChange={onValueChange}\n        required={required}\n        disabled={disabled}\n        id={id}\n      >\n        <BaseSelect.Trigger\n          className={cn(\n            \"border-input bg-background text-foreground flex h-11 w-full cursor-pointer items-center justify-between gap-2 rounded-md border px-3.5 text-left text-base\",\n            \"duration-facade-fast ease-facade-out transition-[border-color,box-shadow]\",\n            \"focus-visible:ring-ring focus-visible:border-ring outline-none focus-visible:ring-2\",\n            \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-60\",\n            \"data-[invalid]:border-destructive data-[invalid]:focus-visible:ring-destructive\",\n            \"data-[placeholder]:text-muted-foreground\",\n            triggerClassName,\n          )}\n        >\n          <BaseSelect.Value className=\"truncate\" />\n          <BaseSelect.Icon className=\"text-muted-foreground flex shrink-0\">\n            <ChevronDownIcon aria-hidden focusable=\"false\" className=\"size-4\" />\n          </BaseSelect.Icon>\n        </BaseSelect.Trigger>\n\n        <BaseSelect.Portal>\n          <BaseSelect.Positioner\n            sideOffset={6}\n            collisionPadding={16}\n            alignItemWithTrigger={false}\n            className=\"z-50\"\n          >\n            <BaseSelect.Popup className=\"bg-popover text-popover-foreground duration-facade-fast ease-facade-out max-h-[var(--available-height)] w-[var(--anchor-width)] origin-[var(--transform-origin)] overflow-y-auto rounded-md border p-1 shadow-lg transition-[opacity,transform] data-[ending-style]:scale-95 data-[starting-style]:scale-95 data-[ending-style]:opacity-0 data-[starting-style]:opacity-0\">\n              <BaseSelect.List className=\"flex flex-col gap-0.5\">\n                {items.map((item) => (\n                  <BaseSelect.Item\n                    key={item.value}\n                    value={item.value}\n                    disabled={item.disabled}\n                    className=\"data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground grid min-h-11 cursor-default select-none grid-cols-[1rem_minmax(0,1fr)] items-center gap-2 rounded-sm px-2 text-base outline-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-60\"\n                  >\n                    <BaseSelect.ItemIndicator className=\"col-start-1 flex\">\n                      <CheckIcon aria-hidden focusable=\"false\" className=\"size-4\" />\n                    </BaseSelect.ItemIndicator>\n                    <BaseSelect.ItemText className=\"col-start-2\">\n                      {item.label}\n                    </BaseSelect.ItemText>\n                  </BaseSelect.Item>\n                ))}\n              </BaseSelect.List>\n            </BaseSelect.Popup>\n          </BaseSelect.Positioner>\n        </BaseSelect.Portal>\n      </BaseSelect.Root>\n\n      {description ? (\n        <Field.Description className=\"text-muted-foreground text-pretty text-sm\">\n          {description}\n        </Field.Description>\n      ) : null}\n\n      {/* `match` shows an error from outside, which Base UI's own validity\n          state knows nothing about. Without one, no children are passed, so\n          Base UI shows the browser's own message for a failed check. */}\n      <Field.Error\n        className=\"text-destructive text-pretty text-sm\"\n        match={Boolean(error) || undefined}\n        {...(error ? { children: error } : {})}\n      />\n    </Field.Root>\n  )\n}\n"
    }
  ],
  "docs": "Import styles/facade-tokens.css in your global stylesheet, after @import \"tailwindcss\". Docs: https://facadeui.dev/components/select"
}
