Thread list

Where past chats live: a dated sidebar, chats filed into projects, a search palette that matches inside messages, a full page for managing chats in bulk, and a slim rail that opens on hover.

Loading preview…

Install

  1. 1

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

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

Usage & API

Example

thread-list-default.tsx
import { ThreadList } from "@/components/ai/thread-list-default"

<ThreadList
  threads={[]}
  activeId={null}
  onSelect={() => {}}
  onNew={() => {}}
  onRename={() => {}}
  onDelete={() => {}}
/>

Props ThreadList

PropType & descriptionRequired
threadsThread[]Required
activeIdstring | nullRequired
onSelect(id: string) => voidRequired
onNew() => voidRequired
onRename(id: string, title: string) => voidRequired
onDelete(id: string) => voidRequired

Dependencies

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

Sidebar exports ThreadList with threads, activeId, onSelect, onNew, onRename, and onDelete. A thread has an id, a title, and a group. The nav is labelled Chats. New chat calls onNew. The search box filters titles in lower case and has a clear button. Each non-empty group gets a heading: Today, Yesterday, or Previous 7 days. The active row sets aria-current page. Rename opens an input labelled Chat title. Enter or blur saves a non-empty trim through onRename. Escape cancels. Delete asks Delete this chat? in the row, with Cancel and Delete, and Escape cancels. The confirm button then calls onDelete. Double-clicking the title starts a rename.

Choosing a layout

Use the sidebar when there is one list and the useful groups are dates. Double-click a title, or use the pencil, to rename. Use Projects when chats belong to named work and people will drag them. A chat with no project and no pin falls into Recent. Use the search palette when a person remembers a phrase from inside a message. The result passes you the chat id and the message index. Use the library when the job is bulk management, not switching chats while writing. Use the rail when the list should get out of the way and come back on hover, with recentCount icons still visible when it is closed.

Do not use the sidebar search as a full-text search. It filters titles only, and the empty copy is No chats match, plus the query. The palette is the one that walks messages. Do not use the library as a narrow sidebar. It is a page: tabs, a sort menu, and a table with Title, Project, Last message, and Updated. Do not invent groups other than the ones each variant has. The sidebar's group field is today, yesterday, or week. The rail's group is the display string Today, Yesterday, or Previous 7 days.

Why does sidebar search miss a phrase I know is in the chat?

ThreadList compares the query with the title only. Message text is not on the Thread type. SearchPalette is the variant that stores messages and matches inside them. It also tells you which message, through the index passed to onOpen. Index -1 means the person opened a chat with no query. The sidebar's empty state prints the query in No chats match. Clearing the field is a button, not a side effect of selecting a chat.

What is the difference between pinning a chat and pinning the rail?

On ProjectSidebar, onTogglePin pins one chat into the pinned block, and the chat's pinned flag is yours to store. On ChatRail, pinned is the panel: true keeps the 260px list open after the pointer leaves, and false lets it collapse back to 56px. recentCount still limits how many icons show in the collapsed rail. The header of a single chat can also pin that chat, but that control lives on Thread header, not here. None of these write to storage.