Thread list
The rows and groups the sidebar is built from: a roving-focus list, date grouping, inline rename, a row menu and a status mark for running, waiting, failed and unread threads.
On this page
Every row state: running, needs input, failed, unread, a long title and an untitled chat.
Installation
Usage
import {
groupThreadsByDate,
ThreadList,
ThreadListGroup,
ThreadListItem,
} from "@/components/ui/chatcn/thread-list"
<ThreadList>
{groupThreadsByDate(threads).map((group) => (
<ThreadListGroup key={group.id} label={group.label}>
{group.threads.map((thread) => (
<ThreadListItem
key={thread.id}
threadId={thread.id}
title={thread.title}
status={thread.status}
active={thread.id === activeId}
onSelect={() => open(thread.id)}
onRename={(title) => rename(thread.id, title)}
onDelete={() => remove(thread.id)}
/>
))}
</ThreadListGroup>
))}
</ThreadList>Use these when your sidebar needs a different shape, for example agent runs grouped by repository. groupThreadsByDate is a plain function; ThreadListSkeleton matches the row layout for loading.
Status
| Status | Mark | Spoken |
|---|---|---|
streaming | Spinner | Generating |
needs-input | Hand, amber | Needs your input |
failed | Alert circle, red | Failed |
unread | Small ink dot | Unread |
idle | Nothing |
Props
ThreadListItem
| Prop | Type | Notes |
|---|---|---|
threadId | string | Used for focus tracking |
title | string | Empty shows placeholder (New chat) |
status | ThreadStatus | See above |
active | boolean | aria-current="page" |
href | string | Renders a link instead of a button |
onSelect | () => void | |
highlight | MatchRange[] | Emphasize filter matches in the title |
onRename, onDelete, onPinChange, onShare | Each adds its menu item | |
menuItems | ReactNode | Extra MenuItems |
renaming, onRenamingChange | Controlled inline rename |
groupThreadsByDate
| Option | Type | Notes |
|---|---|---|
now | Date | number | Reference time. Defaults to now |
pinnedFirst | boolean | Default true |
locale | string | Month names |
labels | object | Rename Pinned, Today, Yesterday and the rest |
Keyboard
| Key | Action |
|---|---|
↑, ↓, Home, End | Move between rows |
Enter | Open the thread |
F2 or double-click | Rename in place. Enter saves, Esc cancels |
Delete | Delete |
Shift+F10, ContextMenu | Open the row menu |
Accessibility
- Rename never saves while an input method is composing, so Japanese, Chinese and Korean titles work.
- On touch screens the menu button is always visible at 44 px, with the status mark beside it.
Motion
- Titles fade out at the end instead of truncating with an ellipsis, mirrored in RTL.
- Keyboard moves between rows are instant and scroll the row into view without smoothing.