---
title: "SearchSelect"
description: "A select that searches as you type and fills in the picked option."
category: Form
status: beta
since: 0.3.4
tags: [search, select, form]
syncStatus: reviewed
verified: true
---

## Usage

### Basic
Local mode filters `options` by `label` and shows them all when the input is empty.
:::TuffDemoWrapper{demo="SearchSelectSearchSelectDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'

  const value = ref<string | number>('')

  const options = [
    { value: 'foo', label: 'Foo' },
    { value: 'bar', label: 'Bar' },
    { value: 'baz', label: 'Baz' },
    { value: 'disabled', label: 'Disabled', disabled: true },
  ]
  </script>

  <template>
    <TxSearchSelect v-model="value" :options="options" placeholder="Search and pick" />
  </template>
---
:::

### Remote Search
`remote` turns off local filtering and emits `search` after the debounce or on Enter; write the results back to `options`.
:::TuffDemoWrapper{demo="SearchSelectSearchSelectRemoteDemo" code-lang="vue"}
---
code: |
  <script setup lang="ts">
  import { ref } from 'vue'

  const value = ref<string | number>('')
  const options = ref<Array<{ value: string, label: string }>>([])
  const loading = ref(false)

  async function onSearch(query: string) {
    loading.value = true
    options.value = await searchApi(query)
    loading.value = false
  }
  </script>

  <template>
    <TxSearchSelect
      v-model="value"
      remote
      :loading="loading"
      :options="options"
      @search="onSearch"
    />
  </template>
---
:::

### Filter Toolbar
`TxSearchSelect` holds structured filters such as scope, type, or status; the keyword goes to `TxSearchInput`, and `TxSearchEmpty` shows when nothing matches.
:::TuffDemoWrapper{demo="ComponentsSearchFiltersDemo" code-lang="vue"}
---
code: |
  <template>
    <TxSearchInput v-model="query" placeholder="Search plugins / docs / tasks" remote />
    <TxSearchSelect
      v-model="scope"
      :options="scopeOptions"
      placeholder="Filter scope"
      panel-background="glass"
    />
    <TxSearchEmpty v-if="!filteredRecords.length" surface="card" />
  </template>
---
:::

### Best Practices

- Keep `options` small in local mode; for large sources use `remote` and write results back from outside.
- Keep each `label` stable and readable; local filtering matches only the label.
- Pick only from a controlled option list; use `TxSearchInput` for free-form keyword search.
- Treat disabled options as explanatory entries, never as the only result.

## API Reference

### Props

| Prop | Type | Default | Description |
|------|------|---------|------|
| `modelValue` | `string \| number` | `''` | Selected value, bound with `v-model`. |
| `placeholder` | `string` | `'Search'` | Placeholder of the input. |
| `disabled` | `boolean` | `false` | Disables the input, panel, and focus-to-open; no remote `search` is emitted. |
| `clearable` | `boolean` | `true` | Shows the clear button, which clears the selected value. |
| `options` | `TxSearchSelectOption[]` | `[]` | Options to choose from; disabled options can't be picked. |
| `loading` | `boolean` | `false` | Shows a spinner in the input suffix. |
| `remote` | `boolean` | `false` | Turns off local filtering and emits `search` instead. |
| `searchDebounce` | `number` | `200` | Debounce of the remote `search`, in milliseconds. |
| `dropdownMaxHeight` | `number` | `280` | Maximum panel height (px); taller content scrolls inside. |
| `dropdownOffset` | `number` | `6` | Gap between the panel and the input. |
| `panelVariant` | `'solid' \| 'dashed' \| 'plain'` | `'solid'` | Panel border style, as TxCard `variant`. |
| `panelBackground` | `'pure' \| 'mask' \| 'blur' \| 'glass' \| 'refraction'` | `'refraction'` | Panel background, as TxCard `background`. |
| `panelShadow` | `'none' \| 'soft' \| 'medium'` | `'soft'` | Panel shadow, as TxCard `shadow`. |
| `panelRadius` | `number` | `18` | Panel corner radius, as TxCard `radius`. |
| `panelPadding` | `number` | `6` | Panel padding, as TxCard `padding`. |

### Events

| Event | Params | Description |
|------|------|------|
| `update:modelValue` | `(v: string \| number)` | Fires after an enabled option is picked or the value is cleared. |
| `change` | `(v: string \| number)` | Fires together with `update:modelValue`. |
| `search` | `(q: string)` | In remote mode, fires after the debounce or on Enter. |
| `select` | `(opt: TxSearchSelectOption)` | Fires with the picked enabled option, before the panel closes. |
| `open` | - | Fires when the panel opens. |
| `close` | - | Fires when the panel closes; cancels any pending remote `search`. |

### Exposed Methods

| Name | Type | Description |
|------|------|-------------|
| `open()` | `() => void` | Opens the panel unless disabled. |
| `close()` | `() => void` | Closes the panel. |
| `focus()` | `() => void` | Focuses the internal `TxInput`. |
| `blur()` | `() => void` | Blurs the internal `TxInput`. |
| `clear()` | `() => void` | Clears the input and the selected value. |

### Types

:::TuffCodeBlock{lang="ts"}
---
code: |
  export interface TxSearchSelectOption {
    value: string | number
    label: string
    disabled?: boolean
  }
---
:::

## Overview

- Remote mode shows `options` as given and caches nothing.
- Clearing emits `update:modelValue` and `change` with `''`.
- The input text survives closing the panel; reopening does not reset it.
- Keyboard: ArrowDown/ArrowUp open the panel and move the highlight through enabled options, wrapping; Enter picks the highlighted option, or emits the remote `search` when none is; Escape closes the panel.

## Technologies

- The panel is hosted by `TxPopover`. No slots are exposed: the prefix, loading suffix, empty state, and option rows render internally to keep keyboard and popover behavior consistent.
- Source: `packages/tuffex/packages/components/src/search-select/`.

<TuffDocSourceLink />
