

<Preview name="DefaultDemo" />

## Overview [#overview]

The `Select` component provides a dropdown interface for choosing from a list of options, with support for grouping, clearing, custom indicators, and advanced rendering. Built on Base UI primitives, it offers full keyboard navigation and accessibility features out of the box.

## Usage [#usage]

```tsx
import {
  Select,
  SelectTrigger,
  SelectValue,
  SelectContent,
  SelectItem,
  SelectGroup,
  SelectLabel,
  SelectSeparator,
  SelectClear,
  SelectIndicator,
} from "@tilt-legal/cubitt-components/primitives";
```

```tsx
// Basic select
const fruitItems = [
  { label: "Apple", value: "apple" },
  { label: "Banana", value: "banana" },
  { label: "Cherry", value: "cherry" },
];

<Select items={fruitItems}>
  <SelectTrigger>
    <SelectValue placeholder="Select a fruit" />
  </SelectTrigger>
  <SelectContent>
    {fruitItems.map((item) => (
      <SelectItem key={item.value} value={item.value}>
        {item.label}
      </SelectItem>
    ))}
  </SelectContent>
</Select>
```

Pass `items` whenever you rely on the default `<SelectValue />` output. Without that label map, the trigger can only fall back to raw values.

<Accordions type="single">
  <Accordion title="Custom Rendering for Trigger Display">
    For complex trigger displays (icons, avatars, badges) use a render function in SelectValue for custom trigger display.

    ```tsx
    const items = [
      { label: "Alan Bold", value: "1", avatar: "https://..." },
      { label: "Nina Clark", value: "2", avatar: "https://..." },
    ];

    <Select items={items} value={value} onValueChange={setValue}>
      <SelectTrigger>
        <SelectValue>
          {(value: string | null) => {
            const selectedItem = items.find((item) => item.value === value);
            return selectedItem ? (
              <span className="flex items-center gap-2">
                <Avatar src={selectedItem.avatar} />
                <span>{selectedItem.label}</span>
              </span>
            ) : (
              <span>Select a user</span>
            );
          }}
        </SelectValue>
      </SelectTrigger>
      <SelectContent>
        {items.map((item) => (
          <SelectItem key={item.value} value={item.value}>
            <span className="flex items-center gap-2">
              <Avatar src={item.avatar} />
              <span>{item.label}</span>
            </span>
          </SelectItem>
        ))}
      </SelectContent>
    </Select>;
    ```
  </Accordion>

  <Accordion title="URL State Management">
    Using Cubitt's URL-state hooks, you can sync the select value with the URL by providing a `paramName`. The component automatically handles array serialization for multi-select mode.

    ```tsx
    // Single select - synced with ?category=electronics
    const categoryItems = [
      { label: "Electronics", value: "electronics" },
      { label: "Books", value: "books" },
      { label: "Clothing", value: "clothing" },
    ];

    <Select
      items={categoryItems}
      paramName="category"
      defaultValue="electronics"
    >
      <SelectTrigger>
        <SelectValue placeholder="Select category" />
      </SelectTrigger>
      <SelectContent>
        {categoryItems.map((item) => (
          <SelectItem key={item.value} value={item.value}>
            {item.label}
          </SelectItem>
        ))}
      </SelectContent>
    </Select>

    // Multi-select - synced with ?tags=react,typescript,ui (comma-separated)
    const tagItems = [
      { label: "React", value: "react" },
      { label: "TypeScript", value: "typescript" },
      { label: "UI/UX", value: "ui" },
    ];

    <Select
      items={tagItems}
      multiple
      paramName="tags"
      defaultValue={[]}
    >
      <SelectTrigger>
        <SelectValue placeholder="Select tags" />
      </SelectTrigger>
      <SelectContent>
        {tagItems.map((item) => (
          <SelectItem key={item.value} value={item.value}>
            {item.label}
          </SelectItem>
        ))}
      </SelectContent>
    </Select>

    // Advanced options:
    <Select
      paramName="filter"
      paramClearOnDefault={true}    // Remove param when value equals default
      paramDebounce={300}           // Debounce URL updates
      onUrlValueChange={(value) => console.log("Selected:", value)}
    >
      {/* ... */}
    </Select>
    ```
  </Accordion>
</Accordions>

## Examples [#examples]

### Indicator Position [#indicator-position]

Move the selection indicator to the left side.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="IndicatorPositionDemo" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      Select,
      SelectContent,
      SelectItem,
      SelectTrigger,
      SelectValue,
    } from "@tilt-legal/cubitt-components/primitives";

    const items = [
      { label: "Apple", value: "apple" },
      { label: "Banana", value: "banana" },
      { label: "Cherry", value: "cherry" },
      { label: "Orange", value: "orange" },
      { label: "Grape", value: "grape" },
    ];

    export default function Component() {
      return (
        <Select items={items} indicatorPosition="left">
          <SelectTrigger className="w-60">
            <SelectValue placeholder="Select a fruit" />
          </SelectTrigger>
          <SelectContent>
            {items.map((item) => (
              <SelectItem key={item.value} value={item.value}>
                {item.label}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      );
    }
    ```
  </Tab>
</Tabs>

### With Clear Button [#with-clear-button]

Add a clear button to reset the selection.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="SelectClearDemo" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      SelectClear,
      SelectContent,
      SelectItem,
      SelectTrigger,
      SelectValue,
    } from "@tilt-legal/cubitt-components/primitives";
    import { useState } from "react";
    import { Select } from "@tilt-legal/cubitt-components/primitives";

    const items = [
      { label: "Apple", value: "apple" },
      { label: "Banana", value: "banana" },
      { label: "Cherry", value: "cherry" },
      // ... more items
    ];

    export default function Component() {
      const [selectedValue, setSelectedValue] = useState<string | null>(null);

      const handleClear = () => {
        setSelectedValue(null);
      };

      const handleValueChange = (value: unknown) => {
        setSelectedValue(value as string | null);
      };

      return (
        <Select
          items={items}
          value={selectedValue}
          onValueChange={handleValueChange}
        >
          <SelectTrigger className="w-60">
            <SelectValue placeholder="Select a fruit" />
            {selectedValue && <SelectClear onClick={handleClear} />}
          </SelectTrigger>
          <SelectContent>
            {items.map((item) => (
              <SelectItem key={item.value} value={item.value}>
                {item.label}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      );
    }
    ```
  </Tab>
</Tabs>

### With Groups [#with-groups]

Organize related options into labeled groups with separators.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="GroupDemo" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      Select,
      SelectContent,
      SelectGroup,
      SelectItem,
      SelectLabel,
      SelectSeparator,
      SelectTrigger,
      SelectValue,
    } from "@tilt-legal/cubitt-components/primitives";

    export default function Component() {
      return (
        <Select>
          <SelectTrigger className="w-60 relative">
            <SelectValue>
              {(value) => {
                if (!value) return "Select framework...";
                const item = frameworkItems.find((item) => item.value === value);
                return item ? item.label : "Select framework...";
              }}
            </SelectValue>
          </SelectTrigger>
          <SelectContent>
            <SelectGroup>
              <SelectLabel>Frontend</SelectLabel>
              <SelectItem value="react">React</SelectItem>
              <SelectItem value="nextjs">Next.js</SelectItem>
              <SelectItem value="angular">Angular</SelectItem>
              <SelectItem value="vue">Vue.js</SelectItem>
              <SelectItem value="svelte">Svelte</SelectItem>
            </SelectGroup>
            <SelectSeparator />
            <SelectGroup>
              <SelectLabel>Backend</SelectLabel>
              <SelectItem value="express">Express</SelectItem>
              <SelectItem value="nestjs">NestJS</SelectItem>
              <SelectItem value="django">Django</SelectItem>
              <SelectItem value="flask">Flask</SelectItem>
              <SelectItem value="laravel">Laravel</SelectItem>
            </SelectGroup>
          </SelectContent>
        </Select>
      );
    }
    ```
  </Tab>
</Tabs>

### Destructive Item [#destructive-item]

Use the `destructive` variant when one option represents a dangerous choice inside an otherwise standard select list.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="SelectDestructiveDemo" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      Select,
      SelectContent,
      SelectItem,
      SelectSeparator,
      SelectTrigger,
      SelectValue,
    } from "@tilt-legal/cubitt-components/primitives";

    const items = [
      { label: "Rename workspace", value: "rename" },
      { label: "Duplicate workspace", value: "duplicate" },
      { label: "Delete workspace", value: "delete" },
    ];

    export default function Component() {
      return (
        <Select items={items}>
          <SelectTrigger className="w-60">
            <SelectValue placeholder="Choose workspace action" />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="rename">Rename workspace</SelectItem>
            <SelectItem value="duplicate">Duplicate workspace</SelectItem>
            <SelectSeparator />
            <SelectItem value="delete" variant="destructive">
              Delete workspace
            </SelectItem>
          </SelectContent>
        </Select>
      );
    }
    ```
  </Tab>
</Tabs>

### Multiple Selection [#multiple-selection]

Enable multi-select mode. Labels are automatically rendered from the `items` prop.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="BaseSelectMultipleDemo" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      Select,
      SelectContent,
      SelectItem,
      SelectTrigger,
      SelectValue,
    } from "@tilt-legal/cubitt-components/primitives";

    const items = [
      { label: "Apple", value: "apple" },
      { label: "Banana", value: "banana" },
      { label: "Cherry", value: "cherry" },
      // ... more items
    ];

    export default function Component() {
      return (
        <Select items={items} defaultValue={["apple", "banana"]} multiple>
          <SelectTrigger className="w-60">
            <SelectValue placeholder="Select fruits" />
          </SelectTrigger>
          <SelectContent>
            {items.map((item) => (
              <SelectItem key={item.value} value={item.value}>
                {item.label}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      );
    }
    ```
  </Tab>
</Tabs>

### Size Variants [#size-variants]

The select trigger supports three size variants: `sm`, `md` (default), and `lg`.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="BaseSelectSizeDemo" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      SelectClear,
      SelectContent,
      SelectItem,
      SelectTrigger,
      SelectValue,
    } from "@tilt-legal/cubitt-components/primitives";
    import { useState } from "react";
    import { Select } from "@tilt-legal/cubitt-components/primitives";

    const items = [
      { label: "Apple", value: "apple" },
      { label: "Banana", value: "banana" },
      { label: "Cherry", value: "cherry" },
      // ... more items
    ];

    export default function Component() {
      const [smValue, setSmValue] = useState<string | null>(null);
      const [mdValue, setMdValue] = useState<string | null>(null);
      const [lgValue, setLgValue] = useState<string | null>(null);

      return (
        <div className="flex flex-col gap-6">
          <Select
            items={items}
            value={smValue}
            onValueChange={(value) => setSmValue(value as string | null)}
          >
            <SelectTrigger size="sm" className="w-60">
              <SelectValue placeholder="Small" />
              {smValue && <SelectClear onClick={() => setSmValue(null)} />}
            </SelectTrigger>
            <SelectContent>
              {items.map((item) => (
                <SelectItem key={item.value} value={item.value}>
                  {item.label}
                </SelectItem>
              ))}
            </SelectContent>
          </Select>

          <Select
            items={items}
            value={mdValue}
            onValueChange={(value) => setMdValue(value as string | null)}
          >
            <SelectTrigger className="w-60">
              <SelectValue placeholder="Medium" />
              {mdValue && <SelectClear onClick={() => setMdValue(null)} />}
            </SelectTrigger>
            <SelectContent>
              {items.map((item) => (
                <SelectItem key={item.value} value={item.value}>
                  {item.label}
                </SelectItem>
              ))}
            </SelectContent>
          </Select>

          <Select
            items={items}
            value={lgValue}
            onValueChange={(value) => setLgValue(value as string | null)}
          >
            <SelectTrigger size="lg" className="w-60">
              <SelectValue placeholder="Large" />
              {lgValue && <SelectClear onClick={() => setLgValue(null)} />}
            </SelectTrigger>
            <SelectContent>
              {items.map((item) => (
                <SelectItem key={item.value} value={item.value}>
                  {item.label}
                </SelectItem>
              ))}
            </SelectContent>
          </Select>
        </div>
      );
    }
    ```
  </Tab>
</Tabs>

### Disabled States [#disabled-states]

Disable the entire select or specific items.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="BaseSelectDisabledDemo" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      Select,
      SelectContent,
      SelectItem,
      SelectTrigger,
      SelectValue,
    } from "@tilt-legal/cubitt-components/primitives";

    const roleItems = [
      { label: "Developer", value: "developer" },
      { label: "Designer", value: "designer" },
      { label: "Manager", value: "manager" },
      { label: "QA Engineer", value: "qa" },
      { label: "Data Analyst", value: "data-analyst" },
    ];

    export default function Component() {
      return (
        <div className="flex flex-col gap-6">
          <Select items={roleItems} disabled>
            <SelectTrigger className="w-60">
              <SelectValue placeholder="Select a role" />
            </SelectTrigger>
            <SelectContent>
              {roleItems.map((item) => (
                <SelectItem key={item.value} value={item.value}>
                  {item.label}
                </SelectItem>
              ))}
            </SelectContent>
          </Select>

          <Select items={roleItems}>
            <SelectTrigger className="w-60">
              <SelectValue placeholder="Select a role" />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="developer">Developer</SelectItem>
              <SelectItem value="designer" disabled>
                Designer
              </SelectItem>
              <SelectItem value="manager">Manager</SelectItem>
              <SelectItem value="qa" disabled>
                QA Engineer
              </SelectItem>
              <SelectItem value="data-analyst">Data Analyst</SelectItem>
            </SelectContent>
          </Select>
        </div>
      );
    }
    ```
  </Tab>
</Tabs>

### With Status Indicators [#with-status-indicators]

Display items with colored status dots.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="BaseSelectStatusDemo" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      SelectClear,
      SelectContent,
      SelectItem,
      SelectTrigger,
      SelectValue,
    } from "@tilt-legal/cubitt-components/primitives";
    import { cn } from "@tilt-legal/cubitt-components/utilities";
    import { useState } from "react";
    import { Select } from "@tilt-legal/cubitt-components/primitives";

    const items = [
      { label: "In Progress", value: "1", state: "bg-violet-500" },
      { label: "Completed", value: "2", state: "bg-green-500" },
      { label: "Pending", value: "3", state: "bg-primary" },
      { label: "Cancelled", value: "4", state: "bg-yellow-500" },
      { label: "Rejected", value: "5", state: "bg-destructive" },
    ];

    export default function Component() {
      const [value, setValue] = useState<string | null>(null);

      return (
        <Select items={items} indicatorPosition="right">
          <SelectTrigger className="w-60">
            <SelectValue placeholder="Select status" />
            {value && <SelectClear onClick={() => setValue(null)} />}
          </SelectTrigger>
          <SelectContent>
            {items.map((item) => (
              <SelectItem key={item.value} value={item.value}>
                <span className="flex items-center gap-2">
                  <span className={cn("size-1.5 rounded-full", item.state)}></span>
                  <span>{item.label}</span>
                </span>
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      );
    }
    ```
  </Tab>
</Tabs>

### With Icons [#with-icons]

Display items with custom icons.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="BaseSelectIconDemo" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      SelectContent,
      SelectItem,
      SelectTrigger,
      SelectValue,
    } from "@tilt-legal/cubitt-components/primitives";
    import { useState } from "react";
    import { Select } from "@tilt-legal/cubitt-components/primitives";
    import {
      SelectClear,
      SelectContent,
      SelectIndicator,
      SelectItem,
      SelectTrigger,
      SelectValue,
    } from "@tilt-legal/cubitt-components/primitives";
    import {
      RiGatsbyLine,
      RiNextjsLine,
      RiReactjsLine } from "@remixicon/react";

    const frameworks = [
      { label: "React",
      value: "react",
      icon: RiReactjsLine },
      { label: "Next.js",
      value: "nextjs",
      icon: RiNextjsLine },
      { label: "Gatsby",
      value: "gatsby",
      icon: RiGatsbyLine },
      ];

    export default function Component() {
      const [value,
      setValue] = useState<string | null>("gatsby");

      const renderValue = (value: string | null) => {
        const item = frameworks.find((i) => i.value === value);
        if (!item) return "Select framework";
        const Icon = item.icon;
        return (
          <span className="flex items-center gap-2">
            <Icon className="size-4 opacity-60" />
            <span>{item.label}</span>
          </span>
        );
      };

      return (
        <Select
          value={value}
          onValueChange={(v) => setValue(v as string | null)}
          indicatorPosition="right"
        >
          <SelectTrigger className="w-60">
            <SelectValue>{renderValue}</SelectValue>
          </SelectTrigger>
          <SelectContent>
            {frameworks.map((item) => {
              const Icon = item.icon;
              return (
                <SelectItem key={item.value} value={item.value}>
                  <span className="flex items-center gap-2">
                    <Icon className="size-4 opacity-60" />
                    <span>{item.label}</span>
                  </span>
                </SelectItem>
              );
            })}
          </SelectContent>
        </Select>
      );
    }
    ```
  </Tab>
</Tabs>

### With Descriptions [#with-descriptions]

Display items with labels and descriptions,
plus custom indicators.

<Tabs
  items="['Preview',
'Code']"
>
  <Tab value="Preview">
    <Preview name="BaseSelectDescriptionDemo" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { useState } from "react";
    import { Select } from "@tilt-legal/cubitt-components/primitives";
    import {
      AvatarFallback,
      AvatarImage,
      Select,
      SelectClear,
      SelectContent,
      SelectGroup,
      SelectItem,
      SelectLabel,
      SelectTrigger,
      SelectValue,
    } from "@tilt-legal/cubitt-components/primitives";
    import {
      RiCheckboxCircleFill } from "@remixicon/react";

    const items = [
      {
        label: "Full access",
      description: "Can modify list access",
      value: "full_access",
      },
      {
        label: "Read and write",
      description: "Can edit & publish lists",
      value: "read_write",
      },
      {
        label: "Read only",
      description: "Can only view lists",
      value: "read_only",
      },
      {
        label: "No access",
      description: "Cannot view or edit lists",
      value: "no_access",
      },
      ];

    export default function Component() {
      const [value,
      setValue] = useState<string | null>(null);

      return (
        <Select
          items={items}
          value={value}
          onValueChange={(value) => setValue(value as string | null)}
          indicatorPosition="right"
          indicator={
            <SelectIndicator>
              <RiCheckboxCircleFill className="size-4 text-primary" />
            </SelectIndicator>
          }
        >
          <SelectTrigger className="w-60">
            <SelectValue placeholder="Select access level" />
            {value && <SelectClear onClick={() => setValue(null)} />}
          </SelectTrigger>
          <SelectContent>
            {items.map((item) => (
              <SelectItem key={item.value} value={item.value}>
                <span className="flex flex-col items-start gap-px">
                  <span className="font-medium">{item.label}</span>
                  <small className="text-muted-foreground text-xs">
                    {item.description}
                  </small>
                </span>
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      );
    }
    ```
  </Tab>
</Tabs>

### With Avatars [#with-avatars]

Display items with user avatars.

<Tabs
  items="['Preview',
'Code']"
>
  <Tab value="Preview">
    <Preview name="BaseSelectAvatarDemo" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import { useState } from "react";
    import { Avatar } from "@tilt-legal/cubitt-components/primitives";

    interface Item {
      label: string;
      avatar: string;
      value: string;
    }

    const items: Item[] = [
      {
        label: "Alan Bold",
        avatar: "https://randomuser.me/api/portraits/men/46.jpg",
        value: "1",
      },
      {
        label: "Ethan James",
        avatar: "https://randomuser.me/api/portraits/men/29.jpg",
        value: "2",
      },
      {
        label: "Nina Clark",
        avatar: "https://randomuser.me/api/portraits/men/10.jpg",
        value: "3",
      },
      {
        label: "Sean Otto",
        avatar: "https://randomuser.me/api/portraits/men/12.jpg",
        value: "4",
      },
    ];

    export default function Component() {
      const [value, setValue] = useState<string | null>(null);

      return (
        <Select
          value={value}
          onValueChange={(value) => setValue(value as string | null)}
          indicatorPosition="right"
        >
          <SelectTrigger className="w-60">
            <SelectValue>
              {(value: string | null) => {
                const selectedItem = items.find((item) => item.value === value);
                return selectedItem ? (
                  <span className="flex items-center gap-2">
                    <Avatar className="size-5">
                      <AvatarImage src={selectedItem.avatar} alt="@reui" />
                      <AvatarFallback>
                        {selectedItem.label.charAt(0)}
                      </AvatarFallback>
                    </Avatar>
                    <span>{selectedItem.label}</span>
                  </span>
                ) : (
                  <span>Select a user</span>
                );
              }}
            </SelectValue>
            {value && <SelectClear onClick={() => setValue(null)} />}
          </SelectTrigger>
          <SelectContent>
            <SelectGroup>
              <SelectLabel className="text-xs py-1 font-normal text-muted-foreground ps-2">
                Select a user
              </SelectLabel>
              {items.map((item) => (
                <SelectItem key={item.value} value={item.value}>
                  <span className="flex items-center gap-2">
                    <Avatar className="size-6">
                      <AvatarImage src={item.avatar} alt="@reui" />
                      <AvatarFallback>{item.label.charAt(0)}</AvatarFallback>
                    </Avatar>
                    <span>{item.label}</span>
                  </span>
                </SelectItem>
              ))}
            </SelectGroup>
          </SelectContent>
        </Select>
      );
    }
    ```
  </Tab>
</Tabs>

### With Badges [#with-badges]

Display items as badge components for statuses.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="BaseSelectBadgeDemo" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      Select,
      SelectClear,
      SelectContent,
      SelectItem,
      SelectTrigger,
      SelectValue,
    } from "@tilt-legal/cubitt-components/primitives";
    import { useState } from "react";
    import { Badge } from "@tilt-legal/cubitt-components/primitives";

    const items = [
      { label: "In Progress", state: "primary", value: "1" },
      { label: "Completed", value: "2", state: "success" },
      { label: "Pending", value: "3", state: "info" },
      { label: "Cancelled", value: "4", state: "destructive" },
      { label: "Rejected", value: "5", state: "warning" },
    ];

    export default function Component() {
      const [value, setValue] = useState<string | null>(null);

      return (
        <Select
          value={value}
          onValueChange={(value) => setValue(value as string | null)}
          indicatorPosition="right"
        >
          <SelectTrigger className="w-60">
            <SelectValue>
              {(value: string | null) => {
                const selectedItem = items.find((item) => item.value === value);
                return selectedItem ? (
                  <Badge
                    variant={selectedItem.state}
                    size="sm"
                    appearance="outline"
                  >
                    {selectedItem.label}
                  </Badge>
                ) : (
                  <span>Select a status</span>
                );
              }}
            </SelectValue>
            {value && <SelectClear onClick={() => setValue(null)} />}
          </SelectTrigger>
          <SelectContent>
            {items.map((item) => (
              <SelectItem key={item.value} value={item.value}>
                <Badge variant={item.state} size="sm" appearance="outline">
                  {item.label}
                </Badge>
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      );
    }
    ```
  </Tab>
</Tabs>

### Scrollable Content [#scrollable-content]

Handle long lists with scrollable content.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="SelectScrollDemo" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      SelectClear,
      SelectContent,
      SelectItem,
      SelectTrigger,
      SelectValue,
    } from "@tilt-legal/cubitt-components/primitives";
    import { useState } from "react";
    import { Select } from "@tilt-legal/cubitt-components/primitives";

    export default function Component() {
      const [value, setValue] = useState<string | null>(null);

      const cities = [
        { label: "Tokyo", value: "tokyo" },
        { label: "New York", value: "new_york" },
        { label: "London", value: "london" },
        // ... more cities
      ];

      return (
        <Select
          items={cities}
          value={value}
          onValueChange={(value) => setValue(value as string | null)}
        >
          <SelectTrigger className="w-60">
            <SelectValue placeholder="Select a city" />
            {value && <SelectClear onClick={() => setValue(null)} />}
          </SelectTrigger>
          <SelectContent className="max-h-56">
            {cities.map((item) => (
              <SelectItem key={item.value} value={item.value}>
                {item.label}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      );
    }
    ```
  </Tab>
</Tabs>

### URL State (Single Select) [#url-state-single-select]

Sync a single selection with the URL. Try selecting a category and refreshing the page.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="SelectSingleURLStateExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      Select,
      SelectContent,
      SelectItem,
      SelectTrigger,
      SelectValue,
    } from "@tilt-legal/cubitt-components/primitives";

    const categories = [
      { label: "Electronics", value: "electronics" },
      { label: "Books", value: "books" },
      { label: "Clothing", value: "clothing" },
      { label: "Home & Garden", value: "home" },
      { label: "Sports", value: "sports" },
    ];

    export default function Component() {
      return (
        <Select
          items={categories}
          paramName="demo-category"
          defaultValue="electronics"
        >
          <SelectTrigger className="w-60">
            <SelectValue placeholder="Select category..." />
          </SelectTrigger>
          <SelectContent>
            {categories.map((item) => (
              <SelectItem key={item.value} value={item.value}>
                {item.label}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      );
    }
    ```
  </Tab>
</Tabs>

### URL State (Multi-Select) [#url-state-multi-select]

Sync multiple selections with the URL using comma-separated values.

<Tabs items="['Preview', 'Code']">
  <Tab value="Preview">
    <Preview name="SelectMultiURLStateExample" />
  </Tab>

  <Tab value="Code">
    ```tsx
    import {
      Select,
      SelectContent,
      SelectItem,
      SelectTrigger,
      SelectValue,
    } from "@tilt-legal/cubitt-components/primitives";

    const tags = [
      { label: "React", value: "react" },
      { label: "TypeScript", value: "typescript" },
      { label: "Tailwind CSS", value: "tailwind" },
      { label: "Next.js", value: "nextjs" },
      { label: "Node.js", value: "nodejs" },
      { label: "UI/UX", value: "ui" },
    ];

    export default function Component() {
      return (
        <Select items={tags} multiple paramName="demo-tags" defaultValue={[]}>
          <SelectTrigger className="w-60">
            <SelectValue placeholder="Select tags..." />
          </SelectTrigger>
          <SelectContent>
            {tags.map((tag) => (
              <SelectItem key={tag.value} value={tag.value}>
                {tag.label}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      );
    }
    ```
  </Tab>
</Tabs>

## API Reference [#api-reference]

### Select [#select]

The root component that provides context for all child components.

| Prop                  | Type                                          | Default   | Description                                                                                                                                  |
| --------------------- | --------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `items`               | `{ label: ReactNode, value: string }[]`       | —         | Label/value pairs used by the trigger display. Pass this whenever you want the default `SelectValue` to render labels instead of raw values. |
| `value`               | `string \| string[] \| null`                  | —         | The controlled value of the select.                                                                                                          |
| `defaultValue`        | `string \| string[] \| null`                  | —         | The default value when uncontrolled.                                                                                                         |
| `onValueChange`       | `(value: string \| string[] \| null) => void` | —         | Callback when the selection changes.                                                                                                         |
| `multiple`            | `boolean`                                     | `false`   | Enable multi-select mode. When true, value/defaultValue should be arrays.                                                                    |
| `disabled`            | `boolean`                                     | `false`   | Disable the select component.                                                                                                                |
| `readOnly`            | `boolean`                                     | `false`   | Make the select read-only (user cannot change selection).                                                                                    |
| `open`                | `boolean`                                     | —         | Control the open state of the dropdown.                                                                                                      |
| `defaultOpen`         | `boolean`                                     | `false`   | The default open state when uncontrolled.                                                                                                    |
| `onOpenChange`        | `(open: boolean) => void`                     | —         | Callback when the open state changes.                                                                                                        |
| `name`                | `string`                                      | —         | The name for form submission.                                                                                                                |
| `required`            | `boolean`                                     | `false`   | Whether the select is required in forms.                                                                                                     |
| `indicatorPosition`   | `"left" \| "right"`                           | `"right"` | Position of the selection indicator within items.                                                                                            |
| `indicatorVisibility` | `boolean`                                     | `true`    | Whether to show the selection indicator.                                                                                                     |
| `indicator`           | `ReactNode`                                   | —         | Custom indicator component to replace the default checkmark.                                                                                 |
| `icon`                | `ReactNode`                                   | —         | Custom icon for the trigger button (replaces default chevron).                                                                               |
| `invalid`             | `boolean`                                     | `false`   | Applies invalid styling and sets `aria-invalid` on the trigger.                                                                              |
| `aria-invalid`        | `AriaAttributes['aria-invalid']`              | —         | Forwarded to the trigger; values `true`, `"grammar"`, or `"spelling"` are treated as invalid.                                                |
| `aria-describedby`    | `string`                                      | —         | Forwarded to the trigger (useful for error message ids).                                                                                     |
| `className`           | `string`                                      | —         | Additional CSS classes for the select root.                                                                                                  |

#### URL State Props [#url-state-props]

When provided with a `paramName`, the select will sync its value with URL parameters via TanStack Router search params.

| Prop                  | Type                                          | Default | Description                                                                  |
| --------------------- | --------------------------------------------- | ------- | ---------------------------------------------------------------------------- |
| `paramName`           | `string`                                      | —       | URL parameter name for syncing state with URL. Enables URL state management. |
| `onUrlValueChange`    | `(value: string \| string[] \| null) => void` | —       | Callback when URL parameter value changes.                                   |
| `paramClearOnDefault` | `boolean`                                     | `true`  | Remove URL param when value equals default.                                  |
| `paramThrottle`       | `number`                                      | —       | Throttle URL updates in milliseconds.                                        |
| `paramDebounce`       | `number`                                      | —       | Debounce URL updates in milliseconds.                                        |

### SelectTrigger [#selecttrigger]

The button that toggles the select dropdown.

| Prop        | Type                   | Default | Description                             |
| ----------- | ---------------------- | ------- | --------------------------------------- |
| `size`      | `"sm" \| "md" \| "lg"` | `"md"`  | The size variant of the trigger.        |
| `className` | `string`               | —       | Additional CSS classes for the trigger. |
| `id`        | `string`               | —       | ID for linking with labels.             |

### SelectValue [#selectvalue]

Displays the selected value or placeholder text. When the `items` prop is provided on the Select root, labels are automatically rendered. For complex displays (icons, avatars, badges), provide a render function as children.

| Prop          | Type                                                              | Default | Description                                                                                               |
| ------------- | ----------------------------------------------------------------- | ------- | --------------------------------------------------------------------------------------------------------- |
| `placeholder` | `string`                                                          | —       | Text to display when no value is selected.                                                                |
| `className`   | `string`                                                          | —       | Additional CSS classes for the value.                                                                     |
| `children`    | `ReactNode \| ((value: string \| string[] \| null) => ReactNode)` | —       | Custom render function for complex displays. Receives the current value and should return React elements. |

### SelectContent [#selectcontent]

The dropdown panel that contains the select options.

| Prop        | Type     | Description                             |
| ----------- | -------- | --------------------------------------- |
| `className` | `string` | Additional CSS classes for the content. |

### SelectItem [#selectitem]

An individual option within the select dropdown.

| Prop        | Type      | Description                          |
| ----------- | --------- | ------------------------------------ |
| `value`     | `string`  | The value this item represents.      |
| `disabled`  | `boolean` | Whether the item is disabled.        |
| `className` | `string`  | Additional CSS classes for the item. |

### SelectGroup [#selectgroup]

Groups related select items together.

| Prop        | Type     | Description                           |
| ----------- | -------- | ------------------------------------- |
| `className` | `string` | Additional CSS classes for the group. |

### SelectLabel [#selectlabel]

A label for a group of select items.

| Prop        | Type     | Description                           |
| ----------- | -------- | ------------------------------------- |
| `className` | `string` | Additional CSS classes for the label. |

### SelectSeparator [#selectseparator]

A visual separator between groups or items.

| Prop        | Type     | Description                               |
| ----------- | -------- | ----------------------------------------- |
| `className` | `string` | Additional CSS classes for the separator. |

### SelectClear [#selectclear]

A button to clear the current selection. Must be positioned within the trigger.

| Prop        | Type         | Description                                  |
| ----------- | ------------ | -------------------------------------------- |
| `onClick`   | `() => void` | Handler for clearing the selection.          |
| `className` | `string`     | Additional CSS classes for the clear button. |

### SelectIndicator [#selectindicator]

Custom indicator component to replace the default checkmark.

| Prop        | Type        | Description                               |
| ----------- | ----------- | ----------------------------------------- |
| `className` | `string`    | Additional CSS classes for the indicator. |
| `children`  | `ReactNode` | Custom indicator content.                 |
