# ComboSelect


`src/lib/components/ui/combo-select/combo-select.svelte`

## Purpose

A searchable single- or multi-select dropdown. Built on the shadcn-svelte™
`Popover` and `Command` primitives (which wrap BITS UI™). Used across the
settings area for role, permission, org, and module selection.

## Origin

**Custom** — fully Primebrick-written. Composes shadcn-svelte™ `Popover` +
`Command` + `Badge`. Not vendored from any registry.

## Usage

### Default (single select, object options)

```svelte
<ComboSelect
  mode="single"
  bind:value
  options={roles}
  valueField="idp_role"
  labelField="label_key"
  isLabelTranslated
  placeholder={$t('settings.selectRole')}
/>
```

### Multi select

```svelte
<ComboSelect
  mode="multi"
  bind:value={selectedRoles}
  options={roles}
  valueField="idp_role"
  labelField="label_key"
  isLabelTranslated
/>
```

### Disabled options

```svelte
<ComboSelect
  mode="single"
  bind:value
  options={roles}
  valueField="idp_role"
  labelField="label_key"
  isOptionDisabled={(o) => o.idp_role === 'admin'}
/>
```

### Trigger variants (`variant`)

The trigger chrome is a closed set, like `Button` variants:

| `variant` | Chrome |
|---|---|
| `'default'` (default) | Primary-gradient border on `bg-background` — the standard form look |
| `'toolbar'` | Neutral InputGroup-style chrome (`border-foreground/25`, transparent bg) for toolbar strips |
| `'ghost'` | The formalized ghost chrome — subtle always-visible `border-foreground/25` on transparent bg (same as the toolbar SearchBar). Shared token: `inputGhostChromeClasses`. For controls embedded inside an already-bordered container (e.g. the color-picker footer) |

```svelte
<ComboSelect mode="single" bind:value options={tokens} variant="ghost" />
```

### Display modes (`display`)

Options are rendered through the typed `display` prop — a closed set, like
`Button` variants. Pick a built-in template instead of writing a snippet.

| `display` | Renders |
|---|---|
| `'default'` (default) | Plain resolved label |
| `'detailed'` | Two-line row — label + secondary value (mono, muted). The secondary line comes from `secondaryField`; when omitted it shows the resolved value. Covers the common "label + key" layout |
| `'badge'` | The option rendered as a colored `Badge`. The color token (e.g. `'emerald-500'`) is read from `colorField` (default `'color'`) |
| `'custom'` | Enables `itemSnippet`/`selectedSnippet` for fully bespoke rows |

```svelte
<!-- Badge options (statuses, recommendations, ...) -->
<ComboSelect
  mode="single"
  bind:value
  display="badge"
  colorField="color"
  options={[{ value: 'ok', label: 'OK', color: 'emerald-500' }]}
  valueField="value"
  labelField="label"
/>

<!-- Two-line label + key rows -->
<ComboSelect
  mode="single"
  bind:value
  display="detailed"
  options={i18nKeys}
  valueField="key"
  labelField="key"
/>
```

### Custom item rendering (`display="custom"`)

`itemSnippet`/`selectedSnippet` are **ignored unless `display="custom"`** —
in dev the component logs a console warning when snippets are passed with
any other display. Use `custom` only for layouts no built-in display covers
(e.g. model cards, avatar rows, conditional badges).

```svelte
<ComboSelect
  mode="single"
  bind:value
  display="custom"
  options={roles}
  valueField="idp_role"
  labelField="label_key"
>
  {#snippet itemSnippet({ option, selected, resolvedLabel })}
    <span class="flex items-center gap-2">
      {#if selected}<Check class="size-4" />{/if}
      {resolvedLabel}
    </span>
  {/snippet}
</ComboSelect>
```

### Keyword boosting

Pass `getSearchKeywords` to let users find an option by fields not present in
the value/label (e.g. searching a role by its permissions). Keywords are passed
to bits-ui's `Command.Item` `keywords` prop for scoring.

```svelte
<ComboSelect
  mode="single"
  bind:value
  options={roles}
  valueField="idp_role"
  labelField="label_key"
  getSearchKeywords={(o) => o.permissions ?? []}
/>
```

## Props

Full prop table: see [API reference — combo-select](/frontend/dev/api-reference#combo-select).

Key props: `mode`, `value` (bindable), `options`, `valueField`, `labelField`,
`isLabelTranslated`, `searchable`, `disabled`, `loading`, `display`,
`colorField`, `secondaryField`, `itemSnippet`, `selectedSnippet` (only with
`display="custom"`), `isOptionDisabled`, `getSearchKeywords`.

## Next steps

- [Component catalog](/frontend/dev/components)
- [UI stack](/frontend/dev/ui-stack)
- [API reference](/frontend/dev/api-reference#combo-select)
