ThreadDropdown
Compose thread switching and creation actions with an unstyled trigger/content pattern.
ThreadDropdown
ThreadDropdown in @tambo-ai/react-ui-base owns thread action behavior (switch, create) while keeping styling and popover/dropdown presentation in @tambo-ai/ui-registry or your own UI layer.
Demo
import { ThreadDropdown } from "@tambo-ai/react-ui-base/thread-dropdown";import { ChevronDown, Plus } from "lucide-react";export function DemoThreadDropdown() { return ( <ThreadDropdown.Root> <ThreadDropdown.Trigger className="flex items-center gap-2 rounded-lg border border-neutral-200 px-3 py-1.5 text-sm text-neutral-700 hover:bg-neutral-100 dark:border-neutral-700 dark:text-neutral-300 dark:hover:bg-neutral-800"> Threads <ChevronDown className="h-3.5 w-3.5" /> </ThreadDropdown.Trigger> <ThreadDropdown.Content render={(props, state) => ( <div {...props} className="mt-1 rounded-lg border border-neutral-200 bg-white p-1 shadow-lg dark:border-neutral-700 dark:bg-neutral-800"> <ThreadDropdown.NewThread className="flex w-full items-center gap-2 rounded-md px-3 py-1.5 text-sm text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-700"> <Plus className="h-3.5 w-3.5" /> New Thread </ThreadDropdown.NewThread> {state.threads.map((thread) => ( <ThreadDropdown.ThreadItem key={thread.id} thread={thread} className="w-full rounded-md px-3 py-1.5 text-left text-sm text-neutral-700 hover:bg-neutral-100 dark:text-neutral-300 dark:hover:bg-neutral-700" > {thread.id} </ThreadDropdown.ThreadItem> ))} </div> )} /> </ThreadDropdown.Root> );}Anatomy
<ThreadDropdown.Root>
<ThreadDropdown.Trigger />
<ThreadDropdown.Content>
<ThreadDropdown.NewThread />
<ThreadDropdown.ThreadItem thread={thread} />
</ThreadDropdown.Content>
</ThreadDropdown.Root>Examples
Thread Change Callback
Respond to thread switches and new thread creation:
<ThreadDropdown.Root onThreadChange={() => console.log("thread changed")}>
{/* ... */}
</ThreadDropdown.Root>Accessing Content State via Render Props
Use the render prop on Content to access threads, loading, and error state:
<ThreadDropdown.Content
render={(props, state) => (
<div {...props}>
{state.isLoading && <p>Loading...</p>}
{state.hasError && <p>Error: {state.error?.message}</p>}
{state.isEmpty && <p>No threads yet</p>}
{state.threads.map((thread) => (
<ThreadDropdown.ThreadItem key={thread.id} thread={thread}>
{thread.id}
</ThreadDropdown.ThreadItem>
))}
</div>
)}
/>Keyboard Shortcut for New Thread
Wire a keyboard shortcut using a ref on NewThread:
const newThreadRef = React.useRef<HTMLButtonElement>(null);
React.useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.altKey && e.shiftKey && e.key === "N") {
e.preventDefault();
newThreadRef.current?.click();
}
};
document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, []);
<ThreadDropdown.NewThread ref={newThreadRef}>
New Thread
</ThreadDropdown.NewThread>;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 | Number of threads in the list. |
isLoading | boolean | Whether the thread list is loading. |
Trigger
No custom props. Renders as <button>. Accepts render and ref via base-ui.
Render state:
| Field | Type | Description |
|---|---|---|
threadCount | number | Number of threads in the list. |
Content
No custom props. Provides thread data and status through render state.
Render state:
| Field | Type | Description |
|---|---|---|
isLoading | boolean | Whether the thread list is loading. |
hasError | boolean | Whether an error occurred. |
isEmpty | boolean | Whether the thread list is empty. |
threads | ThreadListItem[] | Array of thread items. |
error | Error | null | Error from thread list fetch. |
NewThread
No custom props. Renders as <button>. Calls startNewThread() and refetch() on click.
ThreadItem
| Prop | Type | Default | Description |
|---|---|---|---|
thread | ThreadListItem | Required | The thread to display and select. |
Renders as <button>. Calls switchThread() on click.
Render state:
| Field | Type | Description |
|---|---|---|
thread | ThreadListItem | The thread object. |
isActive | boolean | Whether this thread is the currently active one. |
Accessibility
Triggerrenders as a<button>.NewThreadandThreadItemrender as<button>elements for keyboard accessibility.- Consumers are responsible for popover/dropdown ARIA semantics (e.g.,
aria-expanded,role="menu") since this primitive does not impose a specific dropdown pattern.
Styling Hooks
data-slot="thread-dropdown"data-slot="thread-dropdown-trigger"data-slot="thread-dropdown-content"data-slot="thread-dropdown-new-thread"data-slot="thread-dropdown-thread-item"data-activeonThreadItemwhen it is the current thread