ThreadHistory
Compose thread list browsing, search filtering, and thread switching with unstyled parts.
ThreadHistory
ThreadHistory in @tambo-ai/react-ui-base owns thread collection state, search filtering, and selection behavior while keeping styling in @tambo-ai/ui-registry or your own UI layer.
Demo
Loading...
import { ThreadHistory } from "@tambo-ai/react-ui-base/thread-history";import { Plus, Search } from "lucide-react";export function DemoThreadHistory() { return ( <ThreadHistory.Root> <div className="flex flex-col gap-3"> <div className="flex items-center gap-2"> <div className="relative flex-1"> <Search className="absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-neutral-400" /> <ThreadHistory.Search className="w-full rounded-lg border border-neutral-200 bg-transparent py-1.5 pl-8 pr-3 text-sm placeholder:text-neutral-400 focus:outline-none focus:ring-1 focus:ring-neutral-300 dark:border-neutral-700 dark:focus:ring-neutral-600" /> </div> <ThreadHistory.NewThreadButton className="flex h-8 w-8 items-center justify-center rounded-lg bg-neutral-900 text-white hover:bg-neutral-700 dark:bg-neutral-100 dark:text-neutral-900 dark:hover:bg-neutral-300"> <Plus className="h-4 w-4" /> </ThreadHistory.NewThreadButton> </div> <ThreadHistory.List render={(props, state) => ( <div {...props} className="flex flex-col gap-1"> {state.isLoading && <p className="py-2 text-center text-sm text-neutral-500">Loading...</p>} {state.isEmpty && !state.isLoading && <p className="py-2 text-center text-sm text-neutral-500">No threads found</p>} {state.filteredThreads.map((thread) => ( <ThreadHistory.Item key={thread.id} thread={thread} className="w-full rounded-lg px-3 py-2 text-left text-sm text-neutral-700 hover:bg-neutral-100 data-[active]:bg-neutral-100 data-[active]:font-medium dark:text-neutral-300 dark:hover:bg-neutral-800" > {thread.id} </ThreadHistory.Item> ))} </div> )} /> </div> </ThreadHistory.Root> );}Anatomy
<ThreadHistory.Root>
<ThreadHistory.Search />
<ThreadHistory.NewThreadButton />
<ThreadHistory.List>
<ThreadHistory.Item thread={thread} />
</ThreadHistory.List>
</ThreadHistory.Root>Examples
Active Thread Highlighting
Use the data-active attribute on Item to style the currently selected thread:
<ThreadHistory.Item
thread={thread}
className="rounded px-2 py-1 data-active:bg-blue-100"
>
{thread.id}
</ThreadHistory.Item>Thread Change Callback
Respond to thread switches and new thread creation:
<ThreadHistory.Root onThreadChange={() => console.log("thread changed")}>
{/* ... */}
</ThreadHistory.Root>Accessing List State via Render Props
Use the render prop on List to access filtered threads, loading, and error state:
<ThreadHistory.List
render={(props, state) => (
<div {...props}>
{state.isLoading && <p>Loading...</p>}
{state.hasError && <p>Error: {state.error?.message}</p>}
{state.isEmpty && <p>No threads found</p>}
{state.filteredThreads.map((thread) => (
<ThreadHistory.Item key={thread.id} thread={thread}>
{thread.id}
</ThreadHistory.Item>
))}
</div>
)}
/>API reference
Root
| Prop | Type | Default | Description |
|---|---|---|---|
onThreadChange | () => void | undefined | Callback invoked after a thread switch or new thread creation. |
Render state:
| Field | Type | Description |
|---|---|---|
threadCount | number | Count of filtered threads. |
hasSearchQuery | boolean | Whether a search query is active. |
isLoading | boolean | Whether the thread list is loading. |
Search
Renders as <input type="text">. No custom props. Reads and writes the search query from Root context.
Render state:
| Field | Type | Description |
|---|---|---|
searchQuery | string | Current search query. |
List
No custom props. Provides thread data and status through render state.
Render state:
| Field | Type | Description |
|---|---|---|
isEmpty | boolean | Whether filtered threads list is empty. |
isLoading | boolean | Whether the thread list is loading. |
hasError | boolean | Whether an error occurred. |
filteredThreads | ThreadListItem[] | Array of search-filtered thread items. |
searchQuery | string | Current search query. |
error | Error | null | Error from thread list fetch. |
Item
| Prop | Type | Default | Description |
|---|---|---|---|
thread | ThreadListItem | Required | The thread to display and select. |
Renders as <button>. Sets data-active and aria-current="true" when the thread is the current active thread. When no children are provided, renders thread.name as the button label, falling back to thread.id if the name is empty or undefined.
Render state:
| Field | Type | Description |
|---|---|---|
isActive | boolean | Whether this thread is the active thread. |
thread | ThreadListItem | The thread object. |
NewThreadButton
No custom props. Renders as <button>. Calls startNewThread(), refetch(), and onThreadChange() on click.
Render state:
| Field | Type | Description |
|---|---|---|
slot | string | The component slot name. |
Accessibility
Itemrenders as a<button>witharia-current="true"on the active thread for screen-reader accessibility.Searchrenders as an<input>witharia-label="Search threads"built in.NewThreadButtonrenders as a<button>.
Styling Hooks
data-slot="thread-history"data-slot="thread-history-search"data-slot="thread-history-list"data-slot="thread-history-item"data-slot="thread-history-new-thread-button"data-activeonItemwhen thread is activearia-current="true"onItemwhen thread is active