# Search List

> Filter a short list, such as saved prompts or recent threads, from a search field.

Source: https://ai.honestui.com/docs/components/search-list

```tsx
"use client"

import * as React from "react"

import { SearchList, type SearchListItem } from "@/components/ai/search-list"

// Sample saved prompts.
const samplePrompts: SearchListItem[] = [
  { id: "summarize", label: "Summarize this thread", description: "Saved prompt" },
  { id: "review", label: "Review the last change", description: "Saved prompt" },
  { id: "plan", label: "Plan before editing", description: "Saved prompt" },
  { id: "tests", label: "Write tests for this file", description: "Saved prompt" },
  { id: "explain", label: "Explain this error", description: "Saved prompt" },
]

export function SearchListDemo() {
  const [selected, setSelected] = React.useState<SearchListItem | null>(null)

  return (
    <div className="flex w-full max-w-sm min-w-0 flex-col gap-[var(--hui-space-3)]">
      <SearchList
        label="Search saved prompts"
        placeholder="Search saved prompts…"
        items={samplePrompts}
        onSelect={setSelected}
      />
      <p
        role="status"
        className="m-0 text-[var(--hui-color-foreground-base-secondary)] [font-size:var(--hui-font-size-small)]"
      >
        {selected ? `onSelect received “${selected.label}”` : "Nothing selected yet."}
      </p>
    </div>
  )
}

```

## Overview [#overview]

Search List is a search field over one flat list. Typing filters the list, the arrow keys move through it, and choosing an item calls `onSelect`.

It is the Honest UI [Command](https://www.honestui.com/docs/components/command), arranged for this one case. Use Command directly when you need groups, shortcuts, or a dialog.

## Anatomy [#anatomy]

<Anatomy title="SearchList">
  <AnatomyItem name="Field" description="The search input" />

  <AnatomyItem name="Items" description="The matching items, each with an optional icon and description" />

  <AnatomyItem name="Empty" description="A message when nothing matches" />
</Anatomy>

Installing `search-list` brings [Command](https://www.honestui.com/docs/components/command) along as a registry dependency.

## Behavior [#behavior]

### Filtering [#filtering]

Items are matched against their `label`, ignoring case. The description is shown but not searched.

### Empty [#empty]

When nothing matches, `emptyMessage` replaces the list. Say how to recover, such as clearing the search.

## Accessibility [#accessibility]

Search List takes its keyboard and screen reader behavior from Command: the field is a combobox, focus stays in it, and the highlighted item is exposed to assistive technology. Pass `label` to name the field. A placeholder is not a name.

| Key                           | Action                      |
| ----------------------------- | --------------------------- |
| <kbd>↓</kbd> and <kbd>↑</kbd> | Move through the items      |
| <kbd>Enter</kbd>              | Choose the highlighted item |

## Installation [#installation]


  

  
    <CliBlock commands="[&#x22;@honestai/search-list&#x22;]" />
  

  
    
      
        Add the components it is built from.
      

      <CliBlock commands="[&#x22;command&#x22;]" />

      
        Copy and paste this file into your project.
      

      ### components/ai/search-list.tsx

```tsx
"use client"

import * as React from "react"
import { Search as SearchIcon } from "honestui/icons"

import { cn } from "@/lib/utils"
import {
  Command,
  CommandContent,
  CommandEmpty,
  CommandInput,
  CommandItem,
} from "@/components/ui/command"

export type SearchListItem = {
  id: string
  /** Shown in the list and matched against what is typed. */
  label: string
  description?: string
  icon?: React.ReactNode
}

export type SearchListProps = {
  items: SearchListItem[]
  onSelect: (item: SearchListItem) => void
  /** Names the search field for screen readers. */
  label: string
  placeholder?: string
  /** Shown when nothing matches. */
  emptyMessage?: string
  className?: string
}

/**
 * A search field over a short list, such as saved prompts or recent threads.
 * It is the Honest UI Command, arranged for one flat list.
 */
function SearchList({
  items,
  onSelect,
  label,
  placeholder,
  emptyMessage = "No results. Try a different search.",
  className,
}: SearchListProps) {
  return (
    <div
      data-slot="search-list"
      className={cn(
        "w-full min-w-0 overflow-hidden rounded-[var(--hui-radius-4)] border-[0.5px] border-[var(--hui-color-border-base-primary)] bg-[var(--hui-color-background-base-primary)] shadow-[var(--hui-shadow-soft)]",
        className
      )}
    >
      <Command>
        <CommandInput
          aria-label={label}
          // The list is always shown, and a combobox must say whether its list is open.
          aria-expanded
          placeholder={placeholder}
          leadingIcon={<SearchIcon />}
        />
        <CommandContent>
          <CommandEmpty>{emptyMessage}</CommandEmpty>
          {items.map((item) => (
            <CommandItem
              key={item.id}
              value={item.label}
              leadingIcon={item.icon}
              onClick={() => onSelect(item)}
            >
              <span className="flex min-w-0 flex-1 items-baseline justify-between gap-[var(--hui-space-4)]">
                <span className="min-w-0 truncate">{item.label}</span>
                {item.description && (
                  <span className="shrink-0 text-[var(--hui-color-foreground-base-secondary)]">
                    {item.description}
                  </span>
                )}
              </span>
            </CommandItem>
          ))}
        </CommandContent>
      </Command>
    </div>
  )
}

export { SearchList }

```

      
        Update the import paths to match your project setup.
      
    
  


## Usage [#usage]

```tsx
import { SearchList } from "@/components/ai/search-list"

<SearchList
  label="Search saved prompts"
  placeholder="Search saved prompts…"
  items={prompts.map((prompt) => ({ id: prompt.id, label: prompt.title }))}
  onSelect={(item) => composer.insert(item.id)}
/>
```

## Examples [#examples]

### With icons [#with-icons]

Recent threads, with an icon on each item and an empty message that says how to recover.

```tsx
"use client"

import * as React from "react"
import { FileText as FileTextIcon } from "honestui/icons"

import { SearchList } from "@/components/ai/search-list"

export function SearchListThreadsExample() {
  const [opened, setOpened] = React.useState("")

  return (
    <div className="flex w-full max-w-sm min-w-0 flex-col gap-[var(--hui-space-3)]">
      <SearchList
        label="Search recent threads"
        placeholder="Search recent threads…"
        emptyMessage="No threads match. Clear the search to see them all."
        items={[
          { id: "t1", label: "Billing export rounding", icon: <FileTextIcon /> },
          { id: "t2", label: "Staging migration plan", icon: <FileTextIcon /> },
          { id: "t3", label: "Focus ring contrast", icon: <FileTextIcon /> },
        ]}
        onSelect={(item) => setOpened(item.label)}
      />
      <p
        role="status"
        className="m-0 text-[var(--hui-color-foreground-base-secondary)] [font-size:var(--hui-font-size-small)]"
      >
        {opened ? `Opened “${opened}”` : ""}
      </p>
    </div>
  )
}

```

## API reference [#api-reference]

### SearchList [#searchlist]

| Prop           | Type                             | Default  | Description                                                 |
| -------------- | -------------------------------- | -------- | ----------------------------------------------------------- |
| `items`        | `SearchListItem[]`               | Required | Each has `id`, `label`, and optional `description`, `icon`. |
| `onSelect`     | `(item: SearchListItem) => void` | Required | Runs when an item is chosen.                                |
| `label`        | string                           | Required | Names the search field.                                     |
| `placeholder`  | string                           | None     | Shown while the field is empty.                             |
| `emptyMessage` | string                           | English  | Shown when nothing matches.                                 |
| `className`    | string                           | None     | Added to the root.                                          |
