Skip to content
ShellChat searchMenu

Chat search

Full-text search across chats with the matched words marked in titles and snippets, role filters, recent chats and full keyboard control.

Full-text search across chats with the matched words marked in titles and snippets.

Installation

$ pnpm dlx shadcn@latest add https://chatcn.dev/r/chat-search.json

Usage

tsx
import { ChatSearch, ChatSearchDialog } from "@/components/ui/chatcn/chat-search"

<ChatSearchDialog
  open={open}
  onOpenChange={setOpen}
  chats={chats} // { id, title, updatedAt, messages: { id, role, text }[] }[]
  assistantName="chatcn"
  onOpen={(result, { newTab }) => router.push(`/c/${result.chatId}#${result.messageId}`)}
/>

Local search needs every word of the query in one message or in the title, and returns one row per chat with its best message as a snippet. For large histories, search on the server: pass results, update them in onQueryChange (debounced by debounceMs) and set loading while a request is in flight. searchChats is exported if you want the same ranking elsewhere.

Examples

Dialog

The same search in a dialog near the top of the screen, on Cmd+Shift+F.

States

StateShows
IdleRecent chats with their latest message
SearchingPrevious results stay, dimmed; a spinner after 200 ms
ResultsTitle, snippet with marks, who said it, match count and date
No resultsThe query, and a way to search all messages when a filter is on
ErrorWhat went wrong, how to fix it, and Retry search

Props

PropTypeNotes
chatsChatSearchChat[]Searched locally
resultsChatSearchResult[]Server results. Turns local search off
query, defaultQuery, onQueryChangeControlled or uncontrolled
loadingbooleanServer request in flight
error, onRetryboolean | string, () => voidA string replaces the default message
onOpen(result, { newTab }) => voidEnter, click, Cmd/Ctrl+Enter or middle click
assistantNamestringSnippet label and filter name. Default Assistant
roleFilter, role, defaultRole, onRoleChangeAll, Your messages, replies
recentLimitnumberRecent chats while idle. Default 6
now, localeReference time and locale for dates
debounceMsnumberDefault 150
clearOnEscapebooleanEscape clears before anything else. Off in the dialog

Keyboard

KeyAction
↓, ↑Move through results
EnterOpen the chat at the matching message
Cmd+Enter, Ctrl+EnterOpen in a new tab
←, → on the filtersChange the filter
EscClear the query, then close the dialog

Accessibility

  • A combobox with aria-activedescendant: the field keeps focus while the highlight moves.
  • Matches are real mark elements, so they survive copy and forced colors.
  • Filters are a radiogroup with roving focus. The number of chats found is announced once results settle.
  • Errors use role=alert and say how to fix the problem.

Motion

  • Results replace each other without animation: they change on every keystroke.
  • The spinner waits 200 ms, so local search never shows one.
  • The dialog follows the dialog spec from a pointer and is instant from the keyboard.