---
title: MultiSelect
description: Choose multiple collection items in a searchable tag field.
package: moraine
version: 0.6.0
repository: https://github.com/subframe7536/moraine
---

# MultiSelect

> Choose multiple collection items in a searchable tag field.

Use MultiSelect for several selected collection items displayed as tags. Its values resolve through collection items; use [Select](https://moraine.subf.dev/components/select.md) for one choice or [Combobox](https://moraine.subf.dev/components/combobox.md) for editable single selection.

## Basic usage

```tsx
import { Field, MultiSelect } from 'moraine'

export function Example() {
  return (
    <Field label="Skills">
      <MultiSelect
        items={[
          { label: 'Design', value: 'design' },
          { label: 'Code', value: 'code' },
        ]}
        placeholder="Choose skills"
      />
    </Field>
  )
}
```

## Anatomy

```text
MultiSelect [component; no DOM]
├── control [slot]
│   ├── leading [slot]
│   ├── tagsContainer [slot]
│   │   ├── tag [slot]
│   │   │   ├── tagLabel [slot]
│   │   │   └── tagRemove [slot]
│   │   ├── tagOverflow [slot]
│   │   └── input [slot]
│   ├── clear [slot]
│   └── trigger [slot]
└── positioner [internal]
    └── content [slot]
        ├── listbox [slot]
        │   ├── group [slot]
        │   │   └── groupLabel [slot]
        │   └── item [slot]
        │       ├── itemLeading [slot]
        │       ├── itemWrapper [slot]
        │       │   ├── itemLabel [slot]
        │       │   └── itemDescription [slot]
        │       └── itemIndicator [slot]
        └── empty [slot]
```

MultiSelect renders its portaled popup collection internally. Tags have a separate rendering path through `tagRender`.

## Usage

### Values and tag lifecycle

The control slot wraps the interactive tags container and floating anchor. When non-editable, clicking the control opens the panel by default; when editable (`search` or `createItem`), clicking the control focuses the input without opening the panel so users can type immediately. Use `openOnControlClick` to explicitly override this default behavior.

`value` contains the selected item values as an array. An empty array means no selection; an empty
string can be one of the item values. Pair `value` with `onValueChange` for controlled selection.
Without `search`, printable keys trigger collection typeahead. Enabling `search={true}` allows
users to type directly in the field to filter available options.

```tsx
import { Button, MultiSelect } from 'moraine'
import type { MultiSelectT } from 'moraine'
import { createSignal } from 'solid-js'

const FRAMEWORKS: MultiSelectT.Item[] = [
  { label: 'SolidJS', value: 'solid', icon: 'i-lucide:atom' },
  { label: 'Vue.js', value: 'vue', icon: 'i-lucide:sparkles' },
  { label: 'React', value: 'react', icon: 'i-lucide:box' },
  { label: 'Svelte', value: 'svelte', icon: 'i-lucide:flame' },
  { label: 'Astro', value: 'astro', icon: 'i-lucide:rocket' },
]

export function UsageTags() {
  const [selected, setSelected] = createSignal<string[]>(['solid', 'svelte'])

  return (
    <div class="max-w-md w-full space-y-3">
      <MultiSelect
        search
        placeholder="Select frameworks..."
        items={FRAMEWORKS}
        value={selected()}
        onValueChange={setSelected}
        allowClear
      />
      <div class="text-xs flex items-center justify-between">
        <span class="text-muted-foreground">
          Committed Values:{' '}
          <span class="text-foreground font-medium font-mono">{JSON.stringify(selected())}</span>
        </span>
        <Button
          variant="ghost"
          size="sm"
          disabled={selected().length === 0}
          onClick={() => setSelected([])}
        >
          Clear All
        </Button>
      </div>
    </div>
  )
}
```

### Item creation and delimiters

Providing `createItem` transforms unmatched query text into a valid `TItem` on Enter or when a token
separator is encountered. Separator characters (like comma or space) and pasted strings are automatically
parsed through the same creation path.

```tsx
import { MultiSelect } from 'moraine'
import { createSignal } from 'solid-js'

export function UsageCreation() {
  const [tags, setTags] = createSignal<string[]>(['frontend', 'performance'])

  return (
    <div class="max-w-md w-full space-y-2">
      <MultiSelect
        value={tags()}
        onValueChange={setTags}
        createItem={(input) => ({ label: input.trim(), value: input.trim().toLowerCase() })}
        tokenSeparators={[',', ' ']}
        placeholder="Type and press Enter, comma, or space..."
        allowClear
      />
      <p class="text-xs text-muted-foreground">
        Active tags:{' '}
        <span class="text-foreground font-medium font-mono">{tags().join(', ') || '(none)'}</span>
      </p>
    </div>
  )
}
```

### Keyboard interaction

| Key                 | Behavior                                                                   |
| ------------------- | -------------------------------------------------------------------------- |
| Backspace           | When input is empty, deletes the immediately preceding tag                 |
| ArrowDown / ArrowUp | Open popup or move focus through the options list                          |
| Enter               | Commit the highlighted item (or create an item when input text is present) |
| Escape              | Close the dropdown or clear active query text                              |

### Virtualization

Add `createListVirtualizer` when rendering the full collection makes opening or scrolling slow.

Read the [Virtualization guide](https://moraine.subf.dev/docs/virtualization.md) for setup, row measurements, stable keys, and keyboard navigation.

```tsx
import { MultiSelect } from 'moraine'
import type { MultiSelectT } from 'moraine'
import { createListVirtualizer } from 'moraine/virtualizer'

const OPTIONS: MultiSelectT.Item<string>[] = Array.from({ length: 10_000 }, (_, index) => ({
  value: `option-${index}`,
  label: `Option ${index + 1}`,
}))

export function Virtualization() {
  const virtualizer = createListVirtualizer<
    MultiSelectT.Row<MultiSelectT.Item<string>>,
    HTMLDivElement,
    HTMLDivElement
  >({
    estimateSize: (entry) => (entry.type === 'label' ? 30 : 32),
    getItemKey: (entry) => entry.key,
    overscan: 8,
  })

  return (
    <div class="max-w-md w-full">
      <MultiSelect
        items={OPTIONS}
        search
        openOnControlClick
        placeholder="Search across 10,000 options..."
        virtualRender={virtualizer.virtualRender}
        scrollToItem={(_, entryIndex) => virtualizer.scrollToIndex(entryIndex)}
        classes={{ listbox: 'h-80 max-h-80' }}
      />
    </div>
  )
}
```

## Examples

### Custom tag rendering

Use `tagRender` to customize chip presentation with colors, status dots, badges, or custom remove buttons.

```tsx
import { Icon, MultiSelect } from 'moraine'
import type { MultiSelectT } from 'moraine'

interface LabelItem extends MultiSelectT.Item {
  color: string
}

const LABELS: LabelItem[] = [
  {
    label: 'bug',
    value: 'bug',
    color: 'bg-red-500/15 border-red-500/30 text-red-700 dark:text-red-400',
  },
  {
    label: 'feature',
    value: 'feature',
    color: 'bg-purple-500/15 border-purple-500/30 text-purple-700 dark:text-purple-400',
  },
  {
    label: 'documentation',
    value: 'docs',
    color: 'bg-blue-500/15 border-blue-500/30 text-blue-700 dark:text-blue-400',
  },
  {
    label: 'performance',
    value: 'perf',
    color: 'bg-amber-500/15 border-amber-500/30 text-amber-700 dark:text-amber-400',
  },
  {
    label: 'security',
    value: 'security',
    color: 'bg-emerald-500/15 border-emerald-500/30 text-emerald-700 dark:text-emerald-400',
  },
]

export function CustomTags() {
  return (
    <div class="max-w-md w-full space-y-2">
      <label class="text-xs text-muted-foreground font-medium block">Issue Labels</label>
      <MultiSelect<LabelItem>
        items={LABELS}
        defaultValue={['bug', 'perf']}
        search
        openOnControlClick
        placeholder="Select labels..."
        tagRender={(props) => {
          const color = () => props.item?.color ?? 'bg-muted border-border text-foreground'
          return (
            <span
              class={`text-xs font-medium px-2 py-0.5 border rounded-full inline-flex gap-1.5 items-center ${color()}`}
            >
              <span class="rounded-full bg-current opacity-70 h-1.5 w-1.5" />
              <span>{props.label}</span>
              <button
                type="button"
                aria-label={`Remove ${props.value}`}
                onClick={(event) => {
                  event.stopPropagation()
                  props.onClose()
                }}
                class="p-0.5 rounded-full inline-flex transition-opacity items-center justify-center hover:opacity-80"
              >
                <Icon name="i-lucide:x" size={11} />
              </button>
            </span>
          )
        }}
      />
    </div>
  )
}
```

### Project assignees

```tsx
import { MultiSelect } from 'moraine'
import type { MultiSelectT } from 'moraine'

const TEAM_MEMBERS: MultiSelectT.Item[] = [
  {
    label: 'Sarah Connor',
    value: 'sarah',
    icon: 'i-lucide:user',
    description: 'sarah@example.com · Admin',
  },
  {
    label: 'Alex Rivera',
    value: 'alex',
    icon: 'i-lucide:user',
    description: 'alex@example.com · Designer',
  },
  {
    label: 'Elena Rostova',
    value: 'elena',
    icon: 'i-lucide:user',
    description: 'elena@example.com · Engineer',
  },
  {
    label: 'David Kim',
    value: 'david',
    icon: 'i-lucide:user',
    description: 'david@example.com · DevOps',
  },
  {
    label: 'Marcus Vance',
    value: 'marcus',
    icon: 'i-lucide:user',
    description: 'marcus@example.com · QA',
  },
]

export function UserChips() {
  return (
    <div class="max-w-md w-full space-y-2">
      <label class="text-xs text-muted-foreground font-medium block">Project Assignees</label>
      <MultiSelect
        items={TEAM_MEMBERS}
        defaultValue={['sarah', 'alex']}
        search
        openOnControlClick
        placeholder="Assign teammates..."
        allowClear
      />
    </div>
  )
}
```

### Selection and display limits

`maxCount` is a behavioral constraint preventing additional selections. `maxTagCount` is purely
visual: surplus selections stay committed and are summarized in a `+N` badge. Use `tagOverflow`
to render the hidden tags in a Tooltip or another custom presentation.

```tsx
import { Icon, MultiSelect, Tooltip } from 'moraine'
import type { MultiSelectT } from 'moraine'
import { For } from 'solid-js'

const TECH_STACK: MultiSelectT.Item[] = [
  { label: 'SolidJS', value: 'solid', icon: 'i-lucide:atom' },
  { label: 'TypeScript', value: 'ts', icon: 'i-lucide:code' },
  { label: 'Tailwind CSS', value: 'tailwind', icon: 'i-lucide:palette' },
  { label: 'Rust', value: 'rust', icon: 'i-lucide:cog' },
  { label: 'Docker', value: 'docker', icon: 'i-lucide:container' },
]

export function MaxCountMaxTagCount() {
  return (
    <div class="gap-6 grid max-w-2xl w-full sm:grid-cols-2">
      <div class="space-y-1.5">
        <label class="text-xs text-muted-foreground font-medium block">
          Selection Limit (maxCount = 2)
        </label>
        <MultiSelect
          items={TECH_STACK}
          maxCount={2}
          placeholder="Select up to 2 skills..."
          defaultValue={['solid']}
          search
          openOnControlClick
        />
        <p class="text-xs text-muted-foreground">
          Hard cap: prevents adding more once limit is reached.
        </p>
      </div>

      <div class="space-y-1.5">
        <label class="text-xs text-muted-foreground font-medium block">
          Display Limit (maxTagCount = 1)
        </label>
        <MultiSelect
          items={TECH_STACK}
          defaultValue={['solid', 'ts', 'tailwind']}
          maxTagCount={1}
          tagOverflow={(props) => (
            <Tooltip openDelay={200}>
              <Tooltip.Trigger
                as="span"
                class="text-muted-foreground px-1 flex cursor-default items-center"
              >
                +{props.count}
              </Tooltip.Trigger>
              <Tooltip.Content class="p-2 flex gap-1">
                <For each={props.tags}>
                  {(tag) => (
                    <span class="px-2 py-1 rounded bg-muted flex gap-1.5 items-center">
                      {tag.label}
                      <Icon name="i-lucide:x" class="text-muted-foreground size-3.5" />
                    </span>
                  )}
                </For>
              </Tooltip.Content>
            </Tooltip>
          )}
          placeholder="Select tags..."
          search
          openOnControlClick
        />
        <p class="text-xs text-muted-foreground">
          Visual only: hover +N to inspect the collapsed tags.
        </p>
      </div>
    </div>
  )
}
```

### Creatable collection items

Allow users to select from existing options or dynamically generate new tags via an empty state button.

```tsx
import { Button, MultiSelect } from 'moraine'
import type { MultiSelectT } from 'moraine'
import { createSignal } from 'solid-js'

const INITIAL_TOPICS: MultiSelectT.Item[] = [
  { label: 'React', value: 'react' },
  { label: 'SolidJS', value: 'solid' },
  { label: 'Vue.js', value: 'vue' },
  { label: 'TypeScript', value: 'ts' },
]

export function CreateNewTags() {
  const [tags, setTags] = createSignal<MultiSelectT.Item['value'][]>(['solid'])

  return (
    <div class="max-w-md w-full space-y-2">
      <label class="text-xs text-muted-foreground font-medium block">
        Topics (Select existing or type to create)
      </label>
      <MultiSelect
        search
        items={INITIAL_TOPICS}
        value={tags()}
        onValueChange={setTags}
        createItem={(input) => ({ value: input.trim().toLowerCase(), label: input.trim() })}
        tokenSeparators={[',', ';']}
        placeholder="Type to create or select..."
        openOnControlClick
        allowClear
        emptyRender={(ctx) => (
          <div class="p-2 text-center">
            <Button
              variant="link"
              size="sm"
              class="text-xs text-primary"
              onClick={() => ctx.create()}
            >
              Create &ldquo;{ctx.inputValue}&rdquo;
            </Button>
          </div>
        )}
      />
      <p class="text-xs text-muted-foreground">Selected values: {tags().join(', ') || 'none'}</p>
    </div>
  )
}
```

### Free-form tags

Without predefined items, combine `createItem` and `tokenSeparators` for a lightweight tag input.

```tsx
import { MultiSelect } from 'moraine'
import { createSignal } from 'solid-js'

export function FreeFormTags() {
  const [tags, setTags] = createSignal<string[]>(['web', 'ui', 'components'])

  return (
    <div class="max-w-md w-full space-y-2">
      <label class="text-xs text-muted-foreground font-medium block">
        Free-Form Tags (Comma or space separated)
      </label>
      <MultiSelect
        value={tags()}
        onValueChange={setTags}
        createItem={(input) => ({ value: input.trim().toLowerCase(), label: input.trim() })}
        tokenSeparators={[',', ' ']}
        placeholder="Type words, press space or comma..."
        allowClear
      />
      <p class="text-xs text-muted-foreground">Tags array: {JSON.stringify(tags())}</p>
    </div>
  )
}
```

### Grouped options

Organize large collections into categorized option groups.

```tsx
import { MultiSelect } from 'moraine'
import type { MultiSelectT } from 'moraine'
import { createSignal } from 'solid-js'

const PERMISSION_GROUPS: MultiSelectT.Entry[] = [
  {
    label: 'User Management',
    type: 'group',
    items: [
      { label: 'Invite Members', value: 'users:invite', icon: 'i-lucide:user-plus' },
      { label: 'Edit Roles', value: 'users:roles', icon: 'i-lucide:shield' },
      { label: 'Remove Members', value: 'users:remove', icon: 'i-lucide:user-minus' },
    ],
  },
  {
    label: 'Content Management',
    type: 'group',
    items: [
      { label: 'Create Articles', value: 'content:create', icon: 'i-lucide:file-plus' },
      { label: 'Publish Content', value: 'content:publish', icon: 'i-lucide:send' },
      { label: 'Delete Content', value: 'content:delete', icon: 'i-lucide:trash-2' },
    ],
  },
  {
    label: 'Billing & Settings',
    type: 'group',
    items: [
      { label: 'View Invoices', value: 'billing:view', icon: 'i-lucide:receipt' },
      { label: 'Modify Subscriptions', value: 'billing:edit', icon: 'i-lucide:credit-card' },
    ],
  },
]

export function GroupedItems() {
  const [selected, setSelected] = createSignal(['users:invite', 'content:publish'])

  return (
    <div class="max-w-md w-full space-y-2">
      <label class="text-xs text-muted-foreground font-medium block">Role Permissions</label>
      <MultiSelect
        placeholder="Select permissions..."
        items={PERMISSION_GROUPS}
        value={selected()}
        onValueChange={setSelected}
        search
        openOnControlClick
        allowClear
      />
    </div>
  )
}
```

### Clearable, disabled, and locked states

Pre-locked items cannot be deselected, while individual options can be disabled in the listbox.

```tsx
import { MultiSelect } from 'moraine'
import type { MultiSelectT } from 'moraine'

const MODULES: MultiSelectT.Item[] = [
  { label: 'Core Runtime (Locked)', value: 'core', disabled: true },
  { label: 'Analytics Engine', value: 'analytics' },
  { label: 'Push Notifications', value: 'notifications' },
  { label: 'Search Indexer', value: 'search' },
]

export function ClearAndDisabled() {
  return (
    <div class="max-w-md w-full space-y-4">
      <div class="space-y-1.5">
        <label class="text-xs text-muted-foreground font-medium block">
          Disabled Option inside List (Core Runtime cannot be toggled)
        </label>
        <MultiSelect
          items={MODULES}
          defaultValue={['core', 'analytics']}
          search
          openOnControlClick
          placeholder="Select optional modules..."
        />
      </div>

      <div class="space-y-1.5">
        <label class="text-xs text-muted-foreground font-medium block">Disabled Component</label>
        <MultiSelect
          items={MODULES}
          disabled
          defaultValue={['analytics', 'search']}
          placeholder="Disabled"
        />
      </div>

      <div class="space-y-1.5">
        <label class="text-xs text-muted-foreground font-medium block">Read-only Component</label>
        <MultiSelect
          items={MODULES}
          readOnly
          defaultValue={['core', 'notifications']}
          placeholder="Read-only"
        />
      </div>
    </div>
  )
}
```

### Form integration

Validate multi-selection schemas (e.g. requiring a minimum number of tags) with `createForm`.

```tsx
import { Button, createForm, MultiSelect } from 'moraine'
import { createSignal } from 'solid-js'
import * as v from 'valibot'

const TOPICS = [
  { label: 'TypeScript', value: 'typescript' },
  { label: 'SolidJS', value: 'solidjs' },
  { label: 'Tailwind CSS', value: 'tailwind' },
  { label: 'UnoCSS', value: 'unocss' },
  { label: 'Vite', value: 'vite' },
]

export function FormIntegration() {
  const [submittedTags, setSubmittedTags] = createSignal<string[]>([])
  const form = createForm({
    schema: v.object({
      topics: v.pipe(
        v.array(v.string()),
        v.minLength(2, 'Please select at least 2 relevant topics.'),
      ),
    }),
    initialInput: { topics: ['typescript'] },
    validate: 'input',
  })

  return (
    <form.Form onSubmit={(output) => setSubmittedTags(output.topics)}>
      <div class="max-w-xl space-y-4">
        <form.Field
          name="topics"
          label="Interest Topics"
          description="Select at least 2 topics for your curated developer feed."
          required
        >
          <MultiSelect
            items={TOPICS}
            placeholder="Select topics..."
            search
            openOnControlClick
            allowClear
          />
        </form.Field>
        <div class="flex gap-3 items-center">
          <Button type="submit" variant="secondary" size="sm">
            Validate
          </Button>
          <p class="text-xs text-muted-foreground">
            Submitted:{' '}
            <span class="text-foreground font-medium font-mono">
              {submittedTags().length ? submittedTags().join(', ') : 'none'}
            </span>
          </p>
        </div>
      </div>
    </form.Form>
  )
}
```

## Attributes

| Attributes | Slot | Description |
| --- | --- | --- |
| `data-closed` | `multi-select-control`, `multi-select-content`, `multi-select-trigger` | Present when disclosure or transition content is closed. |
| `data-disabled` | `multi-select-control`, `multi-select-item`, `multi-select-trigger` | Present when the component, slot, or item is disabled. |
| `data-editable` | `multi-select-control` | Present when the value can be edited as text. |
| `data-expanded` | `multi-select-control`, `multi-select-content`, `multi-select-trigger` | Present when the panel, accordion, or menu is expanded. |
| `data-invalid` | `multi-select-control`, `multi-select-trigger` | Present when the field or form has a validation error. |
| `data-readonly` | `multi-select-control` | Present when the field is in read-only mode. |
| `data-required` | `multi-select-control` | Present when the field input is required. |
| `data-tags` | `multi-select-control` | Present when the control is displaying selected values as tags. |
| `data-side` | `multi-select-content` | Stores the resolved floating or drawer content side. |
| `data-highlighted` | `multi-select-item`, `multi-select-item-description` | Present when the item is highlighted by pointer or keyboard navigation. |
| `data-selected` | `multi-select-item` | Present when the item or tab is selected. |
| `data-duplicate` | `multi-select-input` | Present when the entered value duplicates an existing value. |
| `data-loading` | `multi-select-trigger` | Present when the component or async operation is loading. |

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| allowClear | boolean \| undefined | false | Show a clear button when a value is selected. |
| autocomplete | JSX.InputHTMLAttributes<HTMLInputElement>['autocomplete'] \| undefined | 'off' | The autocomplete attribute for the search input. |
| closeIcon | IconT.Name \| undefined | — | Icon used when the action button clears the selection.<br>Tag remove buttons keep using this icon as well. |
| createItem | ((input: string) => TItem) \| undefined | — | Factory used for unmatched free-text creation.<br>Providing it also enables the editable search input. |
| defaultOpen | boolean \| undefined | false | Initial popup state. |
| defaultSearchValue | string \| undefined | "" | Initial search text. |
| defaultValue | TItem['value'][] \| undefined | — | The default value of the input (uncontrolled). |
| disabled | boolean \| undefined | false | Whether the input is disabled. |
| emptyRender | JSX.Element \| ((props: EmptyRenderProps<TItem>) => JSX.Element) \| undefined | — | Content or render function for the empty state. |
| filterItem | boolean \| 'startsWith' \| 'endsWith' \| 'contains' \| ((query: string, item: TItem) => boolean) \| undefined | true | Filtering strategy or predicate receiving the raw item. |
| gutter | number \| undefined | 0 | Anchor gap in pixels. |
| id | string \| undefined | — | The ID of the input element. |
| inputRef | Ref<HTMLInputElement> \| undefined | — | Optional editable inner input element ref. Not assigned in non-editable mode. |
| isItemDisabled | ((item: TItem, values: readonly TItem['value'][]) => boolean) \| undefined | — | Additional disabled policy evaluated against the current selection. |
| itemProps | ((state: BaseSelectT.ItemRenderProps<TItem>) => ElementProps<HTMLDivElement> \| undefined) \| undefined | — | Additional row attributes. |
| itemRender | ((props: BaseSelectT.ItemRenderProps<TItem>) => JSX.Element) \| undefined | — | Renderer for each collection item. |
| items | ({<br>  /** Unique selection and form value. */<br>  value: string \| number;<br>  /** Visual label. */<br>  label: JSX.Element;<br>  /** Whether this item cannot be selected. */<br>  disabled?: boolean;<br>  /** Leading item icon. */<br>  icon?: IconT.Name;<br>  /** Secondary item description. */<br>  description?: JSX.Element;<br>} \| {<br>  /** Structural group discriminator. */<br>  type: 'group';<br>  label: JSX.Element;<br>  items: ({<br>    /** Unique selection and form value. */<br>    value: string \| number;<br>    /** Visual label. */<br>    label: JSX.Element;<br>    /** Whether this item cannot be selected. */<br>    disabled?: boolean;<br>    /** Leading item icon. */<br>    icon?: IconT.Name;<br>    /** Secondary item description. */<br>    description?: JSX.Element;<br>  })[];<br>} \| {<br>  /** Structural separator discriminator. */<br>  type: 'separator';<br>})[] \| undefined | — | Source items, optionally grouped. Item values must be unique within the collection. |
| itemToLabelString | ((item: TItem) => string) \| undefined | — | Machine-readable text for matching; does not change visual labels. |
| leadingIcon | IconT.Name \| undefined | — | Icon shown before the input/value area. |
| listboxProps | ElementProps<HTMLDivElement> \| undefined | — | Additional listbox attributes. |
| loading | boolean \| undefined | — | Whether the select is in a loading state. |
| loadingIcon | IconT.Name \| undefined | 'icon-loading' | Icon shown during loading state. |
| maxCount | number \| undefined | — | Maximum number of selected values (multiple/tags). |
| maxTagCount | number \| undefined | — | Maximum visible tags before collapsing the remainder into a +N summary. |
| name | string \| undefined | — | The name of the input element, used for form submission. |
| onClear | (() => void) \| undefined | — | Called when clear is triggered. |
| onOpenChange | ((open: boolean) => void) \| undefined | — | Called when popup state changes. |
| onReset | (() => void) \| undefined | — | Post-reset notification after an unprevented native form reset. |
| onScrollBottom | (() => void) \| undefined | — | Called once when scrolling reaches the bottom. |
| onSearch | ((value: string) => void) \| undefined | — | Called when search text changes. |
| onValueChange | ((value: NoInfer<TItem['value'][]>) => void) \| undefined | — | Called when the selection changes. |
| open | boolean \| undefined | — | Controlled popup state. |
| openOnControlClick | boolean \| undefined | — | Whether ordinary control/input pointer clicks open the popup.<br>Defaults to `false` when editable (`search` or `createItem` enabled),<br>or `true` when non-editable. |
| overflowPadding | number \| undefined | 4 | Collision padding in pixels. |
| placeholder | string \| undefined | "" | Placeholder text shown when no value is selected. |
| readOnly | boolean \| undefined | false | Whether the input is read-only. |
| required | boolean \| undefined | false | Whether the input is required. |
| scrollBottomThreshold | number \| undefined | 20 | Bottom threshold in pixels. |
| scrollToItem | ((item: TItem, entryIndex: number) => void) \| undefined | — | Scroll a highlighted item to its logical entry index. |
| search | boolean \| undefined | false | Whether the collection can be filtered through the editable input. |
| searchMaxLength | number \| undefined | — | Maximum committed search length. |
| searchValue | string \| undefined | — | Controlled search text. |
| size | 'sm' \| 'md' \| 'lg' \| undefined | 'md' | Visual size of the component. |
| tagOverflow | JSX.Element \| ((props: TagOverflowRenderProps<TItem>) => JSX.Element) \| undefined | — | Content or render function for tags hidden by `maxTagCount`. |
| tagRender | ((props: TagRenderProps<TItem>) => JSX.Element) \| undefined | — | Renderer for each selected tag. |
| tokenSeparators | string[] \| undefined | [','] | Strings that commit completed input tokens through `createItem` or an exact source match.<br>Duplicate separators are ignored and overlapping separators prefer the longest match. |
| trailingIcon | IconT.Name \| undefined | 'icon-chevron-down' | Icon used when the action button opens the dropdown. |
| value | TItem['value'][] \| undefined | — | The current value of the input (controlled). |
| variant | 'outline' \| 'subtle' \| 'ghost' \| 'none' \| undefined | 'outline' | Visual treatment of the component. |
| virtualRender | Component<SelectVirtualRenderProps<TItem>> \| undefined | — | Virtual rendering adapter. |
| class | SlotClassValue \| undefined | — | Class applied to the component root or trigger element. |
| classes | Classes \| undefined | — | Family slot class defaults for this instance. |
| style | SlotStyleValue \| undefined | — | Style applied to the component root or trigger element. |
| styles | Styles \| undefined | — | Family slot style defaults for this instance. |
