Chat Sidebar
The sidebar for a chat app. Logo, search and New chat up top, your own links below it, pinned chats, projects that expand to show their chats, recents grouped by day, and rows with hover and right-click menus, inline rename, delete with undo and live reply states.
ChatGPT, Claude, Perplexity and Grok all settled on the same sidebar: start a chat or search from the top, keep a few chats pinned, group work into projects, and scroll back through everything else by day. Chat Sidebar is that sidebar, built on the HextaUI Sidebar’s inset variant, so the conversation sits in its own rounded panel inside the frame. It collapses to an icon rail on desktop, and on phones it slides in to fill the screen and slides away as soon as you pick a chat.
The header holds your logo, a search button and the collapse button. New chat sits below with its shortcut always visible, followed by any links you pass, such as Images, Library or Plugins. Projects are collapsible folders, each with its own icon, and their chats sit indented inside them rather than in the history. Recents group into Today, Yesterday, Previous 7 days, Previous 30 days and then by month, and render in pages as you scroll, so thousands of chats open instantly.
Every row has a menu on hover and the same menu on right-click: pin, rename, move to a project and delete. Rename happens in place with double-click or F2. Delete hides the chat at once and offers Undo for five seconds before calling onDelete. A chat that is replying shows a spinner and one that finished elsewhere shows a dot; both step aside for the menu button on hover. The account menu opens upward with your name and email on top of the items you pass in.
Add the Pro registry to components.json
components.json Add your token
Create a token on your account page and put it in
.env.localasHEXTAUI_PRO_TOKEN.Add the block
pnpm dlx shadcn@latest add @hextaui-pro/chat-sidebar
Wire it to your state
Pass the chats and the handlers you support. Each menu item appears only when its handler is set, so a read-only history needs just onSelect and onNewChat.
Projects and routing
Each project is a collapsible folder with its own icon. Its chats sit indented inside it instead of in the dated history. onMove adds Move to project to every row.
Anatomy
The parts you compose, from the outside in.
| Part | Description |
|---|---|
ChatSidebar | The whole sidebar: header, New chat and links, pinned chats, projects, dated history and the account menu. |
groupChats | Sorts chats newest first and splits them into the dated groups the sidebar shows. |
searchChats | Word-based title search used by the search dialog. Returns the 8 most recent chats for an empty query. |
| Prop | Type | Default |
|---|---|---|
chatsEvery chat, in any order. Each has id, title, updatedAt (ms) and optional projectId, pinned and status ("streaming" or "unread"). | SidebarChat[] | – |
projectsProjects with id, name and an optional icon. Each renders as a collapsible folder holding its chats. The section hides when empty. | SidebarProject[] | [] |
activeIdThe open chat, marked as the current page. | string | null | – |
onSelectCalled when a chat is chosen from the list, search or the recents card in the icon rail. | (id: string) => void | – |
onNewChatCalled from the New chat button and ⌘⇧O. | () => void | – |
linksExtra rows under New chat, such as Images, Library or Plugins. They stay as icons with tooltips in the rail. | { label, icon, href?, active?, onSelect? }[] | [] |
defaultOpenProjectsProjects that start expanded. | string[] | [] |
onProjectOpenChangeCalled when a project is expanded or collapsed, so you can remember it. | (id: string, open: boolean) => void | – |
onRenameEnables Rename, double-click and F2. Called only when the title actually changed. | (id: string, title: string) => void | – |
onPinnedChangeEnables Pin and Unpin. | (id: string, pinned: boolean) => void | – |
onMoveEnables Move to project. null removes the chat from its project. | (id: string, projectId: string | null) => void | – |
onDeleteEnables Delete. Called after the five-second Undo window closes. | (id: string) => void | – |
brandYour logo, at the top next to the search and collapse buttons. | ReactNode | – |
userShows the account button in the footer. | { name, email?, image?, plan? } | – |
accountMenuDropdownMenu items under the name and email, such as Settings, a Help submenu and Log out. | ReactNode | – |
pageSizeHow many history rows render at a time. More load as you near the end of the list. | number | 60 |
classNameClasses for the Sidebar root. | string | – |
| Key | Action |
|---|---|
| ⌘K | Opens or closes chat search. The search button in the header does the same. |
| ⌘⇧O | Starts a new chat. |
| ⌘B | Collapses to the icon rail or expands. On phones, opens or closes the full-screen sidebar. |
| F2 | Renames the focused chat. Double-click does the same. |
| Enter | Expands or collapses the focused project. While renaming, saves the new title; leaving the field also saves. |
| Esc | Cancels a rename, or closes a menu or search. |
- The open chat is marked with aria-current="page", and each row’s menu button is named after its chat, such as “Options for Pricing page copy”. Reply states are announced as “Replying” and “Unread reply” rather than shown only as a spinner or dot.
- Menu buttons appear on hover for mouse users but stay reachable with Tab, and every row action is in that menu, so nothing depends on right-click or double-click. Projects report aria-expanded, and their chats stay findable with the browser’s find once a project has been opened.
- The typing title and the full-screen slide on phones respect reduced motion. Undo stays in the toast until it times out, and focus stays where it was when a chat is deleted.
Code
3 files, added to components/blocks/chat-sidebar.