React UI Base
Loading...

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

PropTypeDefaultDescription
onThreadChange() => voidundefinedCallback invoked after a thread switch or new thread creation.

Render state:

FieldTypeDescription
threadCountnumberNumber of threads in the list.
isLoadingbooleanWhether the thread list is loading.

Trigger

No custom props. Renders as <button>. Accepts render and ref via base-ui.

Render state:

FieldTypeDescription
threadCountnumberNumber of threads in the list.

Content

No custom props. Provides thread data and status through render state.

Render state:

FieldTypeDescription
isLoadingbooleanWhether the thread list is loading.
hasErrorbooleanWhether an error occurred.
isEmptybooleanWhether the thread list is empty.
threadsThreadListItem[]Array of thread items.
errorError | nullError from thread list fetch.

NewThread

No custom props. Renders as <button>. Calls startNewThread() and refetch() on click.

ThreadItem

PropTypeDefaultDescription
threadThreadListItemRequiredThe thread to display and select.

Renders as <button>. Calls switchThread() on click.

Render state:

FieldTypeDescription
threadThreadListItemThe thread object.
isActivebooleanWhether this thread is the currently active one.

Accessibility

  • Trigger renders as a <button>.
  • NewThread and ThreadItem render 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-active on ThreadItem when it is the current thread