# Select

Displays a list of options for the user to pick from, opened from a button.

```tsx
import * as stylex from "@stylexjs/stylex"
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const styles = stylex.create({
  trigger: { width: "100%", maxWidth: "12rem" },
})

const items = [
  { label: "Select a fruit", value: null },
  { label: "Apple", value: "apple" },
  { label: "Banana", value: "banana" },
  { label: "Blueberry", value: "blueberry" },
  { label: "Grapes", value: "grapes" },
  { label: "Pineapple", value: "pineapple" },
]

export default function SelectDemo() {
  return (
    <Select items={items}>
      <SelectTrigger xstyle={styles.trigger} aria-label="Fruit">
        <SelectValue />
      </SelectTrigger>
      <SelectContent>
        <SelectGroup>
          <SelectLabel>Fruits</SelectLabel>
          {items.map((item) => (
            <SelectItem key={item.value} value={item.value}>
              {item.label}
            </SelectItem>
          ))}
        </SelectGroup>
      </SelectContent>
    </Select>
  )
}
```

## Installation

1. Install the following dependencies:

   ```bash
   npm install @base-ui/react @stylexjs/stylex clsx lucide-react
   ```

2. Set up the token layer and the StyleX compiler, if you have not already. See
   [Installation](https://carbon.style/docs/installation), and paste `states.ts` and `state-styles.ts`
   from the same page. `states.ts` holds the popup surface, the item highlight and
   its nested corner, the trigger's field surface, its focus and invalid rings, and
   the dim a disabled trigger or item takes. `state-styles.ts` is how the trigger
   and the items read their Base UI state. Include `interop.css` for icon sizing in the trigger, items and scroll
   arrows.

   Paste `field-state.ts` from the same installation page.

3. Copy and paste the following code into your project.

   ```tsx title="components/ui/select.tsx"
   "use client"

   import { use, type CSSProperties, type ReactNode } from "react"
   import { Select as SelectPrimitive } from "@base-ui/react/select"
   import * as stylex from "@stylexjs/stylex"
   import type { StyleXStyles } from "@stylexjs/stylex"
   import { clsx } from "clsx"
   import { Check, ChevronDown, ChevronUp } from "lucide-react"

   import { stateStyles } from "@/lib/state-styles"
   import { FieldStateContext } from "@/lib/field-state"
   import { states } from "@/theme/states"
   import { color, duration, ease, layer, leading, radius, space, text } from "@/theme/tokens.stylex"

   const fieldHoverBg = `light-dark(${color.buttonSecondaryBg}, color-mix(in oklab, ${color.input} 50%, transparent))`

   const styles = stylex.create({
     hidden: { opacity: 0 },
     trigger: {
       display: "flex",
       width: "fit-content",
       alignItems: "center",
       justifyContent: "space-between",
       gap: space.s1_5,
       whiteSpace: "nowrap",
       borderRadius: radius.lg,
       borderWidth: 1,
       borderStyle: "solid",
       paddingInlineStart: space.s2_5,
       paddingInlineEnd: space.s2,
       paddingBlock: space.s1,
       fontSize: text.sm,
       lineHeight: leading.sm,
       color: color.foreground,
       userSelect: "none",
       transitionProperty: "color, background-color, border-color, box-shadow",
       transitionDuration: duration.color,
       transitionTimingFunction: ease.standard,
       "--carbon-icon-size": space.s4,
     },
     triggerFill: {
       backgroundColor: {
         default: color.fieldBg,
         ":hover": fieldHoverBg,
         ":disabled": color.inputDisabledBg,
       },
     },
     triggerPlaceholder: { color: color.mutedForeground },
     value: {
       display: "block",
       flexGrow: 1,
       minWidth: 0,
       overflow: "hidden",
       textOverflow: "ellipsis",
       textAlign: "start",
     },
     icon: { display: "flex", color: color.mutedForeground },
     content: {
       position: "relative",
       zIndex: layer.popup,
       display: "flex",
       flexDirection: "column",
       width: "var(--anchor-width)",
       minWidth: "9rem",
       maxHeight: "var(--available-height)",
       overflow: "hidden",
       "--carbon-popup-radius": radius.lg,
       borderRadius: "var(--carbon-popup-radius)",
       transformOrigin: "var(--transform-origin)",
       transitionProperty: "opacity, transform",
       transitionDuration: {
         default: duration.fade,
         "@media (prefers-reduced-motion: reduce)": "0s",
       },
       transitionTimingFunction: ease.standard,
     },
     list: { minHeight: 0 },
     scrollArrow: {
       position: "absolute",
       insetInline: 0,
       zIndex: layer.overlay,
       display: "flex",
       alignItems: "center",
       justifyContent: "center",
       paddingBlock: space.s1,
       cursor: "default",
       backgroundColor: color.popover,
       "--carbon-icon-size": space.s4,
     },
     scrollUp: { top: 0 },
     scrollDown: { bottom: 0 },
     group: { padding: space.s1, scrollMarginTop: space.s1, scrollMarginBottom: space.s1 },
     label: {
       paddingInline: space.s1_5,
       paddingBlock: space.s1,
       fontSize: text.xs,
       lineHeight: leading.xs,
       fontWeight: 500,
       color: color.mutedForeground,
     },
     item: {
       position: "relative",
       display: "flex",
       width: "100%",
       alignItems: "center",
       gap: space.s1_5,
       cursor: "default",
       userSelect: "none",
       paddingBlock: space.s1,
       paddingInlineStart: space.s1_5,
       paddingInlineEnd: space.s8,
       fontSize: text.sm,
       lineHeight: leading.sm,
       outlineStyle: "none",
       "--carbon-icon-size": space.s4,
     },
     itemText: {
       display: "flex",
       flexGrow: 1,
       flexShrink: 0,
       alignItems: "center",
       gap: space.s2,
       whiteSpace: "nowrap",
     },
     indicator: {
       pointerEvents: "none",
       position: "absolute",
       insetInlineEnd: space.s2,
       display: "flex",
       width: space.s4,
       height: space.s4,
       alignItems: "center",
       justifyContent: "center",
     },
     separator: { height: 1, backgroundColor: color.border },
   })

   const sizes = stylex.create({
     default: { height: space.s8 },
     sm: { height: space.s7, borderRadius: radius.md },
   })

   const selectStyles = {
     trigger: [styles.trigger, states.focusRing, states.fieldSurface, styles.triggerFill],
     triggerPlaceholder: styles.triggerPlaceholder,
     triggerDisabled: states.disabled,
     invalid: states.invalidRing,
     sizes,
     content: [states.popupSurface, styles.content],
     item: [styles.item, states.popupItem],
     itemHighlighted: states.itemHighlight,
     itemDisabled: states.disabled,
     label: styles.label,
     separator: styles.separator,
   }

   type SelectSize = keyof typeof sizes

   const slide = stylex.create({
     top: { transform: `scale(0.95) translateY(${space.s2})` },
     bottom: { transform: `scale(0.95) translateY(calc(-1 * ${space.s2}))` },
     left: { transform: `scale(0.95) translateX(${space.s2})` },
     right: { transform: `scale(0.95) translateX(calc(-1 * ${space.s2}))` },
     "inline-start": { transform: `scale(0.95) translateX(${space.s2})` },
     "inline-end": { transform: `scale(0.95) translateX(calc(-1 * ${space.s2}))` },
     none: { transform: "none" },
   })

   type Overrides = { xstyle?: StyleXStyles; className?: string }

   type StateOverrides = Overrides & { style?: CSSProperties }

   function Select<Value, Multiple extends boolean | undefined = false>(
     props: SelectPrimitive.Root.Props<Value, Multiple>,
   ) {
     return <SelectPrimitive.Root {...props} />
   }

   type SelectGroupProps = Omit<SelectPrimitive.Group.Props, "className"> & Overrides

   function SelectGroup({ xstyle, className, ...props }: SelectGroupProps) {
     const sx = stylex.props(styles.group, xstyle)
     return (
       <SelectPrimitive.Group
         data-slot="select-group"
         {...sx}
         {...props}
         className={clsx(sx.className, className)}
       />
     )
   }

   type SelectValueProps = Omit<SelectPrimitive.Value.Props, "className"> & Overrides

   function SelectValue({ xstyle, className, ...props }: SelectValueProps) {
     const sx = stylex.props(styles.value, xstyle)
     return (
       <SelectPrimitive.Value
         data-slot="select-value"
         {...sx}
         {...props}
         className={clsx(sx.className, className)}
       />
     )
   }

   type SelectTriggerProps = Omit<SelectPrimitive.Trigger.Props, "className" | "style"> &
     StateOverrides & {
       size?: SelectSize
       icon?: ReactNode
     }

   function SelectTrigger({
     xstyle,
     style,
     className,
     size = "default",
     icon = <ChevronDown />,
     "aria-invalid": ariaInvalid,
     children,
     ...props
   }: SelectTriggerProps) {
     const fieldState = use(FieldStateContext)
     const invalid = ariaInvalid != null && ariaInvalid !== false
     return (
       <SelectPrimitive.Trigger
         data-slot="select-trigger"
         data-size={size}
         aria-invalid={invalid || undefined}
         {...props}
         {...stateStyles(
           (state) => [
             styles.trigger,
             states.focusRing,
             states.fieldSurface,
             styles.triggerFill,
             sizes[size],
             state.placeholder && styles.triggerPlaceholder,
             (state.valid === false || fieldState?.valid === false || invalid) && states.invalidRing,
             state.disabled && states.disabled,
             xstyle,
           ],
           { className, style, hook: "carbon-icons" },
         )}
       >
         {children}
         {icon != null && (
           <SelectPrimitive.Icon data-slot="select-trigger-icon" {...stylex.props(styles.icon)}>
             {icon}
           </SelectPrimitive.Icon>
         )}
       </SelectPrimitive.Trigger>
     )
   }

   type SelectContentProps = Omit<SelectPrimitive.Popup.Props, "className" | "style"> &
     Pick<
       SelectPrimitive.Positioner.Props,
       "align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger"
     > &
     StateOverrides

   function SelectContent({
     xstyle,
     style,
     className,
     children,
     side = "bottom",
     sideOffset = 4,
     align = "center",
     alignOffset = 0,
     alignItemWithTrigger = true,
     ...props
   }: SelectContentProps) {
     return (
       <SelectPrimitive.Portal>
         <SelectPrimitive.Positioner
           side={side}
           sideOffset={sideOffset}
           align={align}
           alignOffset={alignOffset}
           alignItemWithTrigger={alignItemWithTrigger}
           {...stylex.props(states.positioner)}
         >
           <SelectPrimitive.Popup
             data-slot="select-content"
             {...props}
             {...stateStyles(
               (state) => [
                 states.popupSurface,
                 styles.content,
                 (state.transitionStatus === "starting" || state.transitionStatus === "ending") &&
                   styles.hidden,
                 (state.transitionStatus === "starting" || state.transitionStatus === "ending") &&
                   slide[state.side],
                 xstyle,
               ],
               { className, style },
             )}
           >
             <SelectScrollUpButton />
             <SelectPrimitive.List data-slot="select-list" {...stylex.props(styles.list)}>
               {children}
             </SelectPrimitive.List>
             <SelectScrollDownButton />
           </SelectPrimitive.Popup>
         </SelectPrimitive.Positioner>
       </SelectPrimitive.Portal>
     )
   }

   type SelectLabelProps = Omit<SelectPrimitive.GroupLabel.Props, "className"> & Overrides

   function SelectLabel({ xstyle, className, ...props }: SelectLabelProps) {
     const sx = stylex.props(styles.label, xstyle)
     return (
       <SelectPrimitive.GroupLabel
         data-slot="select-label"
         {...sx}
         {...props}
         className={clsx(sx.className, className)}
       />
     )
   }

   type SelectItemProps = Omit<SelectPrimitive.Item.Props, "className" | "style"> & StateOverrides

   function SelectItem({ xstyle, style, className, children, ...props }: SelectItemProps) {
     return (
       <SelectPrimitive.Item
         data-slot="select-item"
         {...props}
         {...stateStyles(
           (state) => [
             styles.item,
             states.popupItem,
             state.highlighted && states.itemHighlight,
             state.disabled && states.disabled,
             xstyle,
           ],
           { className, style, hook: "carbon-icons" },
         )}
       >
         <SelectPrimitive.ItemText data-slot="select-item-text" {...stylex.props(styles.itemText)}>
           {children}
         </SelectPrimitive.ItemText>
         <SelectPrimitive.ItemIndicator
           render={<span data-slot="select-item-indicator" {...stylex.props(styles.indicator)} />}
         >
           <Check />
         </SelectPrimitive.ItemIndicator>
       </SelectPrimitive.Item>
     )
   }

   type SelectSeparatorProps = Omit<SelectPrimitive.Separator.Props, "className"> & Overrides

   function SelectSeparator({ xstyle, className, ...props }: SelectSeparatorProps) {
     const sx = stylex.props(styles.separator, xstyle)
     return (
       <SelectPrimitive.Separator
         data-slot="select-separator"
         {...sx}
         {...props}
         className={clsx(sx.className, className)}
       />
     )
   }

   type SelectScrollUpButtonProps = Omit<SelectPrimitive.ScrollUpArrow.Props, "className"> & Overrides

   function SelectScrollUpButton({ xstyle, className, ...props }: SelectScrollUpButtonProps) {
     const sx = stylex.props(styles.scrollArrow, styles.scrollUp, xstyle)
     return (
       <SelectPrimitive.ScrollUpArrow
         data-slot="select-scroll-up-button"
         {...sx}
         {...props}
         className={clsx("carbon-icons", sx.className, className)}
       >
         <ChevronUp />
       </SelectPrimitive.ScrollUpArrow>
     )
   }

   type SelectScrollDownButtonProps = Omit<SelectPrimitive.ScrollDownArrow.Props, "className"> &
     Overrides

   function SelectScrollDownButton({ xstyle, className, ...props }: SelectScrollDownButtonProps) {
     const sx = stylex.props(styles.scrollArrow, styles.scrollDown, xstyle)
     return (
       <SelectPrimitive.ScrollDownArrow
         data-slot="select-scroll-down-button"
         {...sx}
         {...props}
         className={clsx("carbon-icons", sx.className, className)}
       >
         <ChevronDown />
       </SelectPrimitive.ScrollDownArrow>
     )
   }

   export {
     Select,
     SelectContent,
     SelectGroup,
     SelectItem,
     SelectLabel,
     SelectScrollDownButton,
     SelectScrollUpButton,
     SelectSeparator,
     SelectTrigger,
     SelectValue,
     selectStyles,
     type SelectSize,
   }
   ```

4. Update the import paths to match your project setup.

## Usage

```tsx
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"
```

```tsx
const items = [
  { label: "Theme", value: null },
  { label: "Light", value: "light" },
  { label: "Dark", value: "dark" },
  { label: "System", value: "system" },
]

export function Example() {
  return (
    <Select items={items}>
      <SelectTrigger aria-label="Theme">
        <SelectValue />
      </SelectTrigger>
      <SelectContent>
        <SelectGroup>
          {items.map((item) => (
            <SelectItem key={item.value} value={item.value}>
              {item.label}
            </SelectItem>
          ))}
        </SelectGroup>
      </SelectContent>
    </Select>
  )
}
```

Pass the options through `items` so `SelectValue` can show the chosen item's
label rather than its value. An entry whose `value` is `null` is the
placeholder. Without `items`, pass `placeholder` to `SelectValue` instead.

Give the trigger an `aria-label` or a `Label` with `htmlFor` pointing at its
`id`. There is no `Field` to name it.

Wrap items in a `SelectGroup`, even without a label. The group carries the
popup's inset, so a bare item sits flush against the edge.

The trigger sizes itself to its content. Give it a width through `xstyle` to
stop it changing size when the value does. Right-to-left layout is not covered
here.

## Composition

```text
Select
├── SelectTrigger
│   └── SelectValue
└── SelectContent
    ├── SelectGroup
    │   ├── SelectLabel
    │   ├── SelectItem
    │   └── SelectItem
    ├── SelectSeparator
    └── SelectGroup
        ├── SelectLabel
        ├── SelectItem
        └── SelectItem
```

## Align item with trigger

Set `alignItemWithTrigger` on `SelectContent`. When `true`, the popup opens
over the trigger with the selected item on top of it, the way a native select
does. When `false`, it opens below the trigger like a menu.

```tsx
"use client"

import { useState } from "react"
import * as stylex from "@stylexjs/stylex"
import { Checkbox } from "@/components/ui/checkbox"
import { Label } from "@/components/ui/label"
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"
import { HStack, VStack } from "@/components/ui/stack"

const styles = stylex.create({
  trigger: { width: "100%", maxWidth: "12rem" },
})

const items = [
  { label: "Select a fruit", value: null },
  { label: "Apple", value: "apple" },
  { label: "Banana", value: "banana" },
  { label: "Blueberry", value: "blueberry" },
  { label: "Grapes", value: "grapes" },
  { label: "Pineapple", value: "pineapple" },
]

export default function SelectAlignItem() {
  const [alignItemWithTrigger, setAlignItemWithTrigger] = useState(true)

  return (
    <VStack gap={4} align="start">
      <HStack gap={2} align="center">
        <Checkbox
          id="select-align-item"
          checked={alignItemWithTrigger}
          onCheckedChange={setAlignItemWithTrigger}
        />
        <Label htmlFor="select-align-item">Align item with trigger</Label>
      </HStack>
      <Select items={items} defaultValue="banana">
        <SelectTrigger xstyle={styles.trigger} aria-label="Fruit">
          <SelectValue />
        </SelectTrigger>
        <SelectContent alignItemWithTrigger={alignItemWithTrigger}>
          <SelectGroup>
            {items.map((item) => (
              <SelectItem key={item.value} value={item.value}>
                {item.label}
              </SelectItem>
            ))}
          </SelectGroup>
        </SelectContent>
      </Select>
    </VStack>
  )
}
```

> **Note**
>
> The overlap is skipped on touch, and when there is no room for it. The popup
> then opens from the trigger's edge and slides in from that side instead of
> fading in place.

## Groups

Use `SelectGroup`, `SelectLabel` and `SelectSeparator` to organise items.

```tsx
import * as stylex from "@stylexjs/stylex"
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const styles = stylex.create({
  trigger: { width: "100%", maxWidth: "12rem" },
})

const fruits = [
  { label: "Apple", value: "apple" },
  { label: "Banana", value: "banana" },
  { label: "Blueberry", value: "blueberry" },
]

const vegetables = [
  { label: "Carrot", value: "carrot" },
  { label: "Broccoli", value: "broccoli" },
  { label: "Spinach", value: "spinach" },
]

const items = [{ label: "Select a fruit", value: null }, ...fruits, ...vegetables]

export default function SelectGroups() {
  return (
    <Select items={items}>
      <SelectTrigger xstyle={styles.trigger} aria-label="Produce">
        <SelectValue />
      </SelectTrigger>
      <SelectContent>
        <SelectGroup>
          <SelectLabel>Fruits</SelectLabel>
          {fruits.map((item) => (
            <SelectItem key={item.value} value={item.value}>
              {item.label}
            </SelectItem>
          ))}
        </SelectGroup>
        <SelectSeparator />
        <SelectGroup>
          <SelectLabel>Vegetables</SelectLabel>
          {vegetables.map((item) => (
            <SelectItem key={item.value} value={item.value}>
              {item.label}
            </SelectItem>
          ))}
        </SelectGroup>
      </SelectContent>
    </Select>
  )
}
```

## Scrollable

A list taller than the space below the trigger scrolls. Hovering the arrow at
either end scrolls it. On touch, the arrows do not render.

```tsx
import * as stylex from "@stylexjs/stylex"
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"

const styles = stylex.create({
  trigger: { width: "100%", maxWidth: "16rem" },
})

const regions = [
  {
    label: "North America",
    items: [
      { label: "Eastern Standard Time", value: "est" },
      { label: "Central Standard Time", value: "cst" },
      { label: "Mountain Standard Time", value: "mst" },
      { label: "Pacific Standard Time", value: "pst" },
      { label: "Alaska Standard Time", value: "akst" },
      { label: "Hawaii Standard Time", value: "hst" },
    ],
  },
  {
    label: "Europe & Africa",
    items: [
      { label: "Greenwich Mean Time", value: "gmt" },
      { label: "Central European Time", value: "cet" },
      { label: "Eastern European Time", value: "eet" },
      { label: "Western European Summer Time", value: "west" },
      { label: "Central Africa Time", value: "cat" },
      { label: "East Africa Time", value: "eat" },
    ],
  },
  {
    label: "Asia",
    items: [
      { label: "Moscow Time", value: "msk" },
      { label: "India Standard Time", value: "ist" },
      { label: "China Standard Time", value: "cst_china" },
      { label: "Japan Standard Time", value: "jst" },
      { label: "Korea Standard Time", value: "kst" },
      { label: "Indonesia Central Standard Time", value: "ist_indonesia" },
    ],
  },
  {
    label: "Australia & Pacific",
    items: [
      { label: "Australian Western Standard Time", value: "awst" },
      { label: "Australian Central Standard Time", value: "acst" },
      { label: "Australian Eastern Standard Time", value: "aest" },
      { label: "New Zealand Standard Time", value: "nzst" },
      { label: "Fiji Time", value: "fjt" },
    ],
  },
  {
    label: "South America",
    items: [
      { label: "Argentina Time", value: "art" },
      { label: "Bolivia Time", value: "bot" },
      { label: "Brasilia Time", value: "brt" },
      { label: "Chile Standard Time", value: "clt" },
    ],
  },
]

const items = [
  { label: "Select a timezone", value: null },
  ...regions.flatMap((region) => region.items),
]

export default function SelectScrollable() {
  return (
    <Select items={items}>
      <SelectTrigger xstyle={styles.trigger} aria-label="Timezone">
        <SelectValue />
      </SelectTrigger>
      <SelectContent>
        {regions.map((region) => (
          <SelectGroup key={region.label}>
            <SelectLabel>{region.label}</SelectLabel>
            {region.items.map((item) => (
              <SelectItem key={item.value} value={item.value}>
                {item.label}
              </SelectItem>
            ))}
          </SelectGroup>
        ))}
      </SelectContent>
    </Select>
  )
}
```

## Size

Use the `size` prop on `SelectTrigger`.

```tsx
import * as stylex from "@stylexjs/stylex"
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"
import { HStack } from "@/components/ui/stack"

const styles = stylex.create({
  trigger: { width: "100%", maxWidth: "12rem" },
})

const items = [
  { label: "Select a fruit", value: null },
  { label: "Apple", value: "apple" },
  { label: "Banana", value: "banana" },
  { label: "Blueberry", value: "blueberry" },
]

export default function SelectSize() {
  return (
    <HStack gap={4} align="center" wrap="wrap">
      <Select items={items}>
        <SelectTrigger size="sm" xstyle={styles.trigger} aria-label="Fruit">
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          <SelectGroup>
            {items.map((item) => (
              <SelectItem key={item.value} value={item.value}>
                {item.label}
              </SelectItem>
            ))}
          </SelectGroup>
        </SelectContent>
      </Select>
      <Select items={items}>
        <SelectTrigger size="default" xstyle={styles.trigger} aria-label="Fruit">
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          <SelectGroup>
            {items.map((item) => (
              <SelectItem key={item.value} value={item.value}>
                {item.label}
              </SelectItem>
            ))}
          </SelectGroup>
        </SelectContent>
      </Select>
    </HStack>
  )
}
```

## Disabled

Set `disabled` on `Field` to disable its control and dim its label, or on a bare control directly.

```tsx
import { Field, FieldLabel } from "@/components/ui/field"
import * as stylex from "@stylexjs/stylex"
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"
import { HStack } from "@/components/ui/stack"

const styles = stylex.create({
  trigger: { width: "100%", maxWidth: "12rem" },
})

const items = [
  { label: "Select a fruit", value: null },
  { label: "Apple", value: "apple" },
  { label: "Banana", value: "banana" },
  { label: "Blueberry", value: "blueberry" },
  { label: "Grapes", value: "grapes", disabled: true },
  { label: "Pineapple", value: "pineapple" },
]

export default function SelectDisabled() {
  return (
    <HStack gap={4} align="center" wrap="wrap">
      <Field disabled>
        <FieldLabel>Fruit</FieldLabel>
        <Select items={items}>
          <SelectTrigger xstyle={styles.trigger} aria-label="Fruit">
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            <SelectGroup>
              {items.map((item) => (
                <SelectItem key={item.value} value={item.value}>
                  {item.label}
                </SelectItem>
              ))}
            </SelectGroup>
          </SelectContent>
        </Select>
      </Field>
      <Select items={items}>
        <SelectTrigger xstyle={styles.trigger} aria-label="Fruit">
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          <SelectGroup>
            {items.map((item) => (
              <SelectItem key={item.value} value={item.value} disabled={item.disabled}>
                {item.label}
              </SelectItem>
            ))}
          </SelectGroup>
        </SelectContent>
      </Select>
    </HStack>
  )
}
```

## Invalid

Set `invalid` on `Field` to style its control, or `aria-invalid` on a bare control. The examples show both.

```tsx
import { Field, FieldLabel, FieldError } from "@/components/ui/field"
import * as stylex from "@stylexjs/stylex"
import { Label } from "@/components/ui/label"
import {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select"
import { VStack } from "@/components/ui/stack"

const styles = stylex.create({
  field: { width: "100%", maxWidth: "12rem" },
  trigger: { width: "100%" },
})

const items = [
  { label: "Select a fruit", value: null },
  { label: "Apple", value: "apple" },
  { label: "Banana", value: "banana" },
  { label: "Blueberry", value: "blueberry" },
]

function BareControl({ bare = false }: { bare?: boolean }) {
  return (
    <VStack gap={2} xstyle={styles.field}>
      {bare && <Label htmlFor={bare ? "select-invalid" : undefined}>Fruit</Label>}
      <Select items={items}>
        <SelectTrigger
          id={bare ? "select-invalid" : undefined}
          xstyle={styles.trigger}
          aria-invalid={bare || undefined}
          aria-describedby={bare ? "select-invalid-error" : undefined}
        >
          <SelectValue />
        </SelectTrigger>
        <SelectContent>
          <SelectGroup>
            {items.map((item) => (
              <SelectItem key={item.value} value={item.value}>
                {item.label}
              </SelectItem>
            ))}
          </SelectGroup>
        </SelectContent>
      </Select>
      <FieldError id={bare ? "select-invalid-error" : undefined}>Please select a fruit.</FieldError>
    </VStack>
  )
}

export default function InvalidExample() {
  return (
    <VStack gap={5}>
      <BareControl bare />
      <Field invalid>
        <FieldLabel>Fruit</FieldLabel>
        <BareControl />
      </Field>
    </VStack>
  )
}
```

## API reference

Pass `render` to replace a part's element or compose it with another component.

Every part wraps the Base UI `Select` part of the same name and accepts its
props. See the [Base UI reference](https://base-ui.com/react/components/select)
for the full list. Each styled part also takes `xstyle` and `className`.
`SelectTrigger` and `SelectItem` take `style` too. It is real inline CSS, merged
over whatever their Base UI state resolves to.

### Select

| Prop            | Type                                                        | Default |
| --------------- | ----------------------------------------------------------- | ------- |
| `items`         | `{ label: ReactNode; value: Value }[] \| Record<string, …>` | —       |
| `value`         | `Value \| null`                                             | —       |
| `defaultValue`  | `Value \| null`                                             | —       |
| `onValueChange` | `(value: Value \| null) => void`                            | —       |
| `multiple`      | `boolean`                                                   | `false` |
| `name`          | `string`                                                    | —       |
| `required`      | `boolean`                                                   | `false` |
| `disabled`      | `boolean`                                                   | `false` |
| `readOnly`      | `boolean`                                                   | `false` |

`name` renders a hidden input, so the value travels with a form.

### SelectTrigger

| Prop           | Type                | Default     |
| -------------- | ------------------- | ----------- |
| `size`         | `"default" \| "sm"` | `"default"` |
| `icon`         | `ReactNode`         | chevron     |
| `aria-invalid` | `boolean`           | —           |
| `disabled`     | `boolean`           | —           |
| `style`        | `CSSProperties`     | —           |

Text inside the trigger clips with an ellipsis once it outgrows the width the
trigger was given.

`icon` replaces the chevron after the value. Pass `null` to remove it.

### SelectValue

| Prop          | Type                                | Default |
| ------------- | ----------------------------------- | ------- |
| `placeholder` | `ReactNode`                         | —       |
| `children`    | `ReactNode \| (value) => ReactNode` | —       |

A function child formats the selected value. It replaces the label `items`
would otherwise provide.

### SelectContent

| Prop                   | Type                                                                       | Default    |
| ---------------------- | -------------------------------------------------------------------------- | ---------- |
| `side`                 | `"top" \| "bottom" \| "left" \| "right" \| "inline-start" \| "inline-end"` | `"bottom"` |
| `sideOffset`           | `number`                                                                   | `4`        |
| `align`                | `"start" \| "center" \| "end"`                                             | `"center"` |
| `alignOffset`          | `number`                                                                   | `0`        |
| `alignItemWithTrigger` | `boolean`                                                                  | `true`     |

The popup is the trigger's width and never narrower than `9rem`. Its height is
capped at the space available, and the list inside scrolls.

### SelectItem

| Prop       | Type            | Default |
| ---------- | --------------- | ------- |
| `value`    | `Value`         | —       |
| `disabled` | `boolean`       | `false` |
| `label`    | `string`        | —       |
| `style`    | `CSSProperties` | —       |

`label` is the text typed letters match against. It defaults to the item's
text content.

### selectStyles

The style maps behind the component. Spread them onto any element.

| Key                  | What it is                                            |
| -------------------- | ----------------------------------------------------- |
| `trigger`            | The trigger's frame and type, ring, surface and hover |
| `triggerPlaceholder` | The muted text while nothing is selected              |
| `triggerDisabled`    | The dimmed, inert state every control shares          |
| `invalid`            | The destructive border and ring                       |
| `sizes`              | `default` and `sm` heights                            |
| `content`            | The popup: the shared surface and the box over it     |
| `item`               | An item's row, type and indicator inset               |
| `itemHighlighted`    | The highlight fill and inset ring                     |
| `itemDisabled`       | The same dim, on one option                           |
| `label`              | A group label                                         |
| `separator`          | The hairline between groups                           |

`trigger` is the part's own box, `states.focusRing`, `states.fieldSurface` and
the fill with its hover, in merge order. `content` merges `states.popupSurface`
(the fill and hairline ring) under the popup's own box, which sets
`--carbon-popup-radius`. `item` merges `states.popupItem`, so a row's corner is
that radius minus the popup's inset. `itemHighlighted` is
`states.itemHighlight`.
`invalid` is `states.invalidRing` and both disabled keys are `states.disabled`.
Set a trigger border of your own through `--carbon-border-color` rather than
`borderColor`. A flat `borderColor` replaces the property with all of its conditions, so the ring loses its focus colour.

### Data attributes

| Attribute                                  | On          | Effect                                                                      |
| ------------------------------------------ | ----------- | --------------------------------------------------------------------------- |
| `data-slot="select-trigger"`               | the trigger | Targets the trigger from a parent                                           |
| `data-size`                                | the trigger | Reflects the `size` prop                                                    |
| `data-placeholder`                         | the trigger | Base UI writes it while nothing is selected                                 |
| `data-popup-open`, `data-disabled`         | the trigger | Base UI writes them                                                         |
| `data-slot="select-content"`               | the popup   | Popup surface                                                               |
| `data-side`, `data-align`                  | the popup   | Base UI writes them. `data-side` picks the slide; `"none"` means overlapped |
| `data-starting-style`, `data-ending-style` | the popup   | Base UI writes them while the popup animates in or out                      |
| `data-slot="select-item"`                  | an item     | Targets items from a parent                                                 |
| `data-highlighted`, `data-selected`        | an item     | Base UI writes them                                                         |
| `data-disabled`                            | an item     | Base UI writes it                                                           |
