---
title: "SearchPanel"
description: "An inline search card with a field, live results, and an empty state."
category: Advanced
status: beta
since: 0.3.9
tags: [search, filter, listbox, command]
syncStatus: reviewed
verified: true
---

## Usage

### Live Filtering
:::TuffDemoWrapper{demo="SearchPanelSearchPanelDemo" code-lang="vue"}
---
code: |
  <template>
    <TxSearchPanel v-model="query" :items="items" @select="run" />
  </template>

  <script setup lang="ts">
  const items = [
    { id: 'a', label: 'Forecast summer demand' },
    { id: 'b', label: 'Find waffle cone suppliers', keywords: ['vendor'] },
    { id: 'c', label: 'Retire low sellers', disabled: true },
  ]
  </script>
---
:::

### Best Practices

- Use `TxCommandPalette` for a globally invoked launcher and this panel for search that stays on the page; both share one keyboard contract.
- As a launcher, run the action in `select` and clear the query; as a filter, keep the query visible.
- For remote search, pass `:filter="items => items"` to disable the built-in match and debounce your own fetch on `queryChange`.
- Put synonyms in `keywords`, and mark unavailable entries `disabled` instead of removing them from `items`.
- Size `minHeight` for your most common result count; too small brings the jumping back.

## API Reference

### Props

| Prop | Type | Default | Description |
|------|------|---------|------|
| `modelValue` | `string` | `''` | Query text, bound with `v-model`. |
| `items` | `SearchPanelItem[]` | `[]` | Candidate items. |
| `placeholder` | `string` | `'Search'` | Field placeholder. |
| `ariaLabel` | `string` | - | Accessible name of the field; falls back to `placeholder`. |
| `idleCount` | `number` | `5` | Items shown for an empty query; `0` shows all. |
| `emptyThreshold` | `number` | `3` | Shortest query that may show the empty state. |
| `emptyTitle` | `string` | `'No results found'` | Empty-state title. |
| `emptyDescription` | `string` | `'Adjust your search to try again'` | Empty-state description. |
| `clearLabel` | `string` | `'Clear search'` | Accessible name of the clear button. |
| `listLabel` | `string` | `'Search results'` | Accessible name of the result list. |
| `minHeight` | `number \| string` | `248` | Reserved height, so results don't resize the panel. |
| `filter` | `(items, query) => items` | - | Replaces the built-in match: case-insensitive `includes` over `label` and `keywords`. |
| `clearable` | `boolean` | `true` | Shows the clear button. |
| `disabled` | `boolean` | `false` | Disables the field. |

### Events

| Event | Payload | Description |
|------|------|------|
| `update:modelValue` | `string` | Fires when the query changes. |
| `queryChange` | `string` | Same value as `update:modelValue`, for hosts without `v-model`. |
| `select` | `SearchPanelItem` | Fires when a result is clicked or chosen with Enter. |
| `clear` | - | Fires when the clear button or Escape empties the field. |

### Slots

| Slot | Scope | Description |
|------|------|------|
| `item` | `{ item, active, query }` | Replaces a row's content. |
| `empty` | `{ query }` | Replaces the empty state. |
| `footer` | - | Content below the list, inside the card. |

### Exposed Methods

| Name | Description |
|------|------|
| `focus()` / `blur()` | Focuses or blurs the field. |
| `clear()` | Empties the query and emits `clear`. |

### Types

| Name | Description |
|------|------|
| `SearchPanelItem` | `{ id, label, keywords?, disabled? }`; `keywords` join the built-in match but aren't displayed. |

## Overview

- Renders inline: no overlay, no modal semantics, no focus trap — it fits in a page, sidebar, or popover.
- ↑ ↓ wrap and skip disabled rows, Home / End jump to the first and last enabled rows, Enter selects, and Escape clears when there is text. Enter is ignored during IME composition.
- Follows the combobox pattern: the field is `role="combobox"` with `aria-autocomplete="list"`, `aria-controls`, and `aria-activedescendant`; rows are `role="option"` with `tabindex="-1"`, so focus stays in the field.
- The highlight is `is-active`, and pointer movement syncs it to the row under the mouse.
- Selecting a result doesn't write it back into the field; the host handles `select`.
- Below `emptyThreshold` characters, a query with no matches doesn't show the empty state.

## Technologies

- The empty state reuses `TxSearchEmpty`, tuned only through its public CSS variables and `icon` slot.
- Adapted from [Beautiful UI](https://www.beautifului.dev), © 2026 Shane Levine, MIT; keyboard navigation and ARIA are additions.
- Source: `packages/tuffex/packages/components/src/search-panel/`.

<TuffDocSourceLink />
