Conversation

The message area of a chat: one that follows new text down, one with edited prompts that branch, two models answering side by side, a side thread for a follow-up, and an agent transcript with tool calls folded in.

Loading preview…

Install

  1. 1

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

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

Usage & API

Example

conversation-default.tsx
import { Conversation } from "@/components/ai/conversation-default"

<Conversation
  children={children}
/>

Props Conversation

PropType & descriptionRequired
childrenReactNodeRequired
classNamestringHeight of the scroll area.Optional
thresholdnumberDistance from the bottom, in px, that still counts as "at the bottom".Optional

Dependencies

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

Stick to bottom exports Conversation with children, className, default h-[360px], and threshold, default 32. The scroller is role log and tabIndex 0. A ResizeObserver on the inner content scrolls to the bottom when the stick flag is set. Scrolling up past threshold clears that flag and shows Latest, with an arrow, centred on the bottom edge. Clicking it smooth-scrolls to the end and sets the flag again. A fade covers the top 32px of the box so text does not meet the rim. The component does not know what a message is. The preview puts bubbles in children and appends to them.

Choosing a layout

Use Stick to bottom for an ordinary streaming reply, when the failure mode is the reader being dragged downward while they are reading something higher up. Use Branches when a person can edit a prompt and you need the earlier answers to remain reachable. The control reads 2/3 once there are three versions. Use Side by side when two or more models answer the same prompt and someone has to choose. Use the side thread when a follow-up is about one quoted paragraph and should not join the main transcript. The header says Not added to the main chat. Use the agent transcript when the reply is a run: files read, a command, a diff, and a pause for Allow or Deny.

Do not use the agent transcript as a generic tool-call card with no surrounding conversation. The steps are the conversation: a user step, a text step, a tools step, an edit step, an approval step. A lone approval with no transcript is a different screen. Do not use Side by side to compare two drafts of the same model. It labels each column with answer.model and calls onPrefer with the answer id. Do not put a side thread's messages into the main children and also into the panel. The panel is explicit that those replies stay separate.

Why does the thread jump when I scroll up to read?

Conversation only sticks while the reader is within threshold of the bottom. The default is 32px. If your message list grows by more than that between frames, or if something above the fold changes height, measure again: handleScroll sets the flag from scrollHeight, scrollTop, and clientHeight. While the flag is off, growth does not move scrollTop, and Latest is the way back. There is no prop to force stick. If you need to jump to an older prompt instead, that control is Scroll rail, which calls onSelect with the turn id.

Does editing a message replace the old answer?

No. BranchedMessage calls onEdit with the new text and leaves the branches array to you. The hint in the editor says the other versions stay. BranchNav then moves active among them. Sending the same text, or an empty trim, only closes the editor. The assistant turns live in branch.after, and renderAfter is how you draw them, so each version can have its own reply. Copy message copies the prompt that is on screen, not every branch.