v1.5

Chat List View

List of chat threads for a history sidebar, with a selected row, previews, timestamps and per-row actions.

Pro

Description

ChatListView renders a conversation history as a <ul>. Each ChatListView.Item is an <li> that holds one full-row ChatListView.ItemButton (a <button>, or your link through asChild) and an optional ChatListView.Actions group next to it. Inside the button, Icon, ItemContent, Title, Preview and Meta lay out the thread. It is a compound component with no client state, so it renders fine as a Server Component.

Use it for the thread list in an AI assistant sidebar, a support inbox's conversation column, or a "recent chats" panel on a dashboard. The row tokens match the vertical-button row of TabMenu, which is what Sidebar uses, so a chat history sits next to app navigation without looking like a second design.

Don't use it for app navigation with sections and collapse behaviour: that is Sidebar. For switching between views in place, use TabMenu. For a nested hierarchy, use TreeView, and for picking a value from a list, use Select or Combobox.

ChatListView is a Create UI Pro component. Install it with a Pro seat.

Installation

pnpm dlx @create-ui/cli add chat-list-view

Anatomy

<ChatListView>
  <ChatListView.Item>
    <ChatListView.ItemButton>
      <ChatListView.Icon />
      <ChatListView.ItemContent>
        <ChatListView.Title />
        <ChatListView.Preview />
      </ChatListView.ItemContent>
      <ChatListView.Meta />
    </ChatListView.ItemButton>
    <ChatListView.Actions />
  </ChatListView.Item>
</ChatListView>

Usage

import { ChatListView } from "@/components/ui/chat-list-view"
<ChatListView aria-label="Recent chats">
  <ChatListView.Item>
    <ChatListView.ItemButton selected>
      <ChatListView.ItemContent>
        <ChatListView.Title>Product launch planning</ChatListView.Title>
      </ChatListView.ItemContent>
    </ChatListView.ItemButton>
  </ChatListView.Item>
</ChatListView>

Examples

Every example is a Pro preview (marked with a Pro badge).

Sizes

size is set once on the root. md (default) is a two-line row with a 20px icon. sm is a single-line row with a 16px icon that hides Preview but keeps Meta, so timestamps and unread counts survive in a narrow sidebar.

Pro

Icons

ChatListView.Icon renders a chat glyph when it has no children. Pass your own icon to mark the thread type, or leave the part out for a text-only row. On the selected row the icon turns text-primary-base.

Pro

States

selected marks the open thread with a bg-weak fill and aria-current. disabled dims every part to text-disabled and blocks the pointer, which suits archived or read-only threads.

Pro

With asChild, the row renders your <a> or router link. selected sets aria-current="page" on it, and disabled swaps the native attribute for aria-disabled plus tabIndex={-1}, since links have no disabled.

Pro

Row actions

ChatListView.Actions sits beside the row button, so rename, pin and delete never nest a button inside a button. With the default reveal="hover" the group stays collapsed until the row is hovered or focused, stays open while its menu is open, and is always visible on touch screens. Use reveal="always" to pin it open.

Pro

Grouped history

Split threads by date with one ChatListView per group, each labelled by its heading through aria-labelledby, and wrap the groups in a <nav>. New threads are prepended and selected.

Pro

Accessibility

Every row button is a separate tab stop, and so is each control inside ChatListView.Actions. The list does not add roving focus or arrow key handling, which keeps it predictable next to links and menus.

KeyDescription
TabMoves focus to the next row button, then into that row's actions.
Shift + TabMoves focus back to the previous control.
EnterActivates the focused row (button or link).
SpaceActivates the focused row when it renders as a <button>.

ARIA notes:

  • The root is a <ul role="list"> and each item is an <li>. The explicit role keeps list semantics in Safari, which drops them when list-style is removed.
  • Give the root an aria-label, or wrap the list in a labelled <nav> or point aria-labelledby at a visible group heading.
  • selected sets aria-current="true" on a button and aria-current="page" on an asChild link.
  • disabled sets the native disabled attribute on a button. On a link it sets aria-disabled="true" and tabIndex={-1}.
  • ChatListView.Icon is aria-hidden. Put the thread name in Title so it becomes the row's accessible name.
  • Icon-only buttons in Actions need a specific aria-label, such as More actions for Product launch planning, so screen reader users can tell rows apart.
  • Hidden actions are collapsed with opacity and width, not display, so they stay in the tab order and the accessibility tree. Focusing into them reveals them.
  • Colour and opacity transitions are turned off under prefers-reduced-motion.

Styling

Tailwind override: every part merges className through cn():

<ChatListView.Title className="font-medium">
  Product launch planning
</ChatListView.Title>

Data slots and attributes: the component sets these for CSS targeting:

  • data-slot="chat-list-view" on the root <ul>, with data-size ("md" or "sm").
  • data-slot="chat-list-view-item" on each <li>. It carries the selected fill and the focus outline.
  • data-slot="chat-list-view-item-button" on the row button or link, with data-selected present on the open thread.
  • data-slot="chat-list-view-icon" on the leading icon box.
  • data-slot="chat-list-view-item-content" on the title and preview column.
  • data-slot="chat-list-view-title", data-slot="chat-list-view-preview" and data-slot="chat-list-view-meta" on the text parts.
  • data-slot="chat-list-view-actions" on the actions group, with data-reveal ("hover" or "always").
[data-slot="chat-list-view-item-button"][data-selected]
  [data-slot="chat-list-view-title"] {
  /* ... */
}

States: the <li> paints the fill and the outline, and the row button sets the text colour that Title inherits.

StateAttributeRow
Default-no fill, title text-body
Hover:hover on the itemtitle text-strongest, hidden actions reveal
Focused:focus-visible on the row button2px inset primary-700 outline around the whole item
Selecteddata-selectedbg-weak, title text-strongest, icon primary-base
Disableddisabled or aria-disabled="true"every part text-disabled, no pointer events
  • Sidebar: the app shell and navigation around a chat history, with groups, collapse and tooltips.
  • Tab Menu: switches between views in place; shares the same row tokens.
  • Tree View: use it when threads live inside folders or projects.
  • Dropdown Menu: the menu to put inside ChatListView.Actions.

API Reference

ChatListView

The list root. Extends React.ComponentProps<"ul">. Also available as ChatListView.Root and ChatListViewRoot.

Props

PropTypeDefaultDescription
size"md" | "sm""md"Row scale, read by every part through CSS.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-ChatListView.Item elements.

Variants

VariantOptionsDefaultDescription
size"md" "sm""md"md is a two-line row with a 20px icon. sm is one line, 16px icon, no Preview.

ChatListView.Item

One thread. Renders an <li> and extends React.ComponentProps<"li">. It paints the selected fill and focus outline for the row button and actions together.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-One ChatListView.ItemButton and an optional ChatListView.Actions.

ChatListView.ItemButton

The full-row control. Renders a <button type="button"> and extends React.ComponentProps<"button">, or renders its child through Radix Slot with asChild.

Props

PropTypeDefaultDescription
selectedbooleanfalseMarks the open thread. Sets data-selected, aria-current and the bg-weak fill.
disabledbooleanfalseNative disabled on a button, aria-disabled plus tabIndex={-1} with asChild.
asChildbooleanfalseRender the child element (a link or router link) instead of a <button>.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Icon, ItemContent and Meta, or a single link element when asChild is set.

ChatListView.Icon

Leading icon box. Renders an aria-hidden <span> and extends React.ComponentProps<"span">.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode<RiChat1Line />The glyph. Sized by the root size.

ChatListView.ItemContent

The column that holds Title and Preview and lets them truncate. Renders a <span> and extends React.ComponentProps<"span">.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-ChatListView.Title and ChatListView.Preview.

ChatListView.Title / ChatListView.Preview / ChatListView.Meta

The thread name, the last message snippet and the trailing timestamp or count. Each renders a <span> and extends React.ComponentProps<"span">. Title and Preview truncate to one line. Preview is hidden at size="sm"; Meta stays visible at both sizes.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-The text content.

ChatListView.Actions

Trailing controls for one thread, rendered as a sibling of the row button. Renders a <div> and extends React.ComponentProps<"div">.

Props

PropTypeDefaultDescription
reveal"hover" | "always""hover"When the group is visible. See Variants.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Icon buttons or a Dropdown with an icon button trigger.

Variants

VariantOptionsDefaultDescription
reveal"hover" "always""hover"hover collapses the group until the row is hovered, focused or has an open menu, and always shows on touch.

Types and helpers

type ChatListViewSize = "md" | "sm"

chatListViewVariants, chatListViewItemVariants, chatListViewItemButtonVariants and chatListViewActionsVariants are exported CVA helpers for styling your own elements to match. Every part also has a named export (ChatListViewItem, ChatListViewItemButton, and so on) and a matching ...Props type.