Thread header

The bar above a chat: title and model with a share and options menu, a project breadcrumb with who can see it, a status bar for an agent working on a task, and a header that tucks away as you read.

Loading preview…

Install

  1. 1

    Run this command in your Tailwind CSS project. It adds components/ai/thread-header-default.tsx and installs its dependencies.

    $ npx shadcn@latest add https://gradientdeck.com/ai-components/r/thread-header-default.json

Usage & API

Example

thread-header-default.tsx
import { ThreadHeader } from "@/components/ai/thread-header-default"

<ThreadHeader
  title="…"
  onTitleChange={() => {}}
  models={[]}
  model="…"
  onModelChange={() => {}}
  projects={[]}
  project={null}
  onProjectChange={() => {}}
  pinned={true}
  onAction={() => {}}
  onShare={() => {}}
  sidebarOpen={true}
  onSidebarToggle={() => {}}
/>

Props ThreadHeader

PropType & descriptionRequired
titlestringRequired
onTitleChange(title: string) => voidRequired
modelsThreadModel[]Required
modelstringRequired
onModelChange(id: string) => voidRequired
projectsThreadProject[]Required
projectstring | nullRequired
onProjectChange(id: string | null) => voidRequired
pinnedbooleanRequired
archivedbooleanOptional
onAction(action: ThreadAction) => voidRequired
onShare() => voidRequired
sidebarOpenbooleanRequired
onSidebarToggle() => voidRequired

Dependencies

  • React
  • Tailwind CSS
  • motion
  • lucide-react
Implementation notes

Header bar exports ThreadHeader. Props are title, onTitleChange, models, model, onModelChange, projects, project, onProjectChange, pinned, archived, onAction, onShare, sidebarOpen, and onSidebarToggle. A model has id, name, and note. A project has id, name, and color. The sidebar button reads Open sidebar or Close sidebar. The title becomes an input labelled Chat title. Enter saves a changed non-empty trim. Escape cancels. The model button's accessible name is Model: plus the current name. Share calls onShare, then shows Copied for 1600ms, and its accessible name becomes Link copied. The component does not write the clipboard itself. The options menu includes Rename, Pin or Unpin, Move to project with a nested list and Remove from project, Export as Markdown, Archive or Unarchive, and Delete. Choosing the current project again clears it through onProjectChange(null).

Choosing a layout

Use the header bar on a normal chat where the person renames the thread, switches the model for this chat only, and shares or files it. The delete confirm says Delete this chat and its files? You can undo for a few seconds. The action you receive is pin, export, archive, or delete. Use the project variant when the chat lives in a workspace and access is the point: who is invited, whether they can edit or only view, and whether the link is private, for the team, or public. Use the agent variant when the header should say what the run is doing. Use the scroll-aware header when the title should stay available without occupying a row, and the transcript scrolls inside the component.

Do not put run controls on the ordinary header. Stop, Resume, and Open PR exist only on the agent variant, and they call onStop, onResume, and onOpenPr. Do not use the scroll-aware header if the page around it scrolls and the transcript does not. It listens to scroll inside its own scroller, and the pill appears as that value moves from 36px to 104px. The full title fades across the first 84px. A list of chats does not belong in the bar. The sidebar toggle calls onSidebarToggle. The sidebar itself is Thread list.

The agent header says Done but the progress bar is still coloured. Is progress required to be 1?

progress is yours, from 0 to 1, and the bar scales to it. status only picks the colour and the buttons: indigo and Stop while running, amber and Resume while stopped, emerald and the done actions when done. When status is done the bar also fades to about 35 percent opacity after a short hold. The component will not set progress to 1 for you. formatElapsed does not read progress. It only formats elapsed. Review changes lists the files you passed, with each path, added, and removed.

The pill never appears, or it appears immediately. What is it watching?

Scroll inside ScrollAwareThread, not the window. The scroller is the div around children. The pill's progress runs from scroll offset 36 to 104, and the large header's opacity runs from 0 to 84. The switch that marks the header inert happens when that 36-to-104 value passes one half. If children do not overflow, scroll stays at 0 and the pill stays hidden. footer is outside the scroller, so a composer there does not count toward the scroll height. messages is only the number printed in the header. It is not used to decide when to condense.