When to use
- The message list of any AI chat fed by
useChat(). Responseon its own anywhere a model writes markdown.
When not to use
- A static transcript with no streaming: render Message in a plain list.
Installation
pnpm dlx shadcn add @eased-pro/conversationnpx shadcn add @eased-pro/conversationyarn dlx shadcn add @eased-pro/conversationbunx --bun shadcn add @eased-pro/conversationNeeds the @eased-pro registry and your Pro token in components.json. Create a token and copy the setup from your account .
Install the dependencies
pnpm add motion streamdownnpm install motion streamdownyarn add motion streamdownbun add motion streamdownAdd the Eased pieces it builds on
Copy each item’s files the same way, or install them with the CLI:
Add the files
- components/eased/conversation/conversation.tsx
- components/eased/conversation/response.tsx
Source is included with Eased Pro. Members download the files with the CLI command above, or through the MCP server.
Keep the folder layout
The files import each other by relative path (
../lib/motion), so keep each one at the path shown, undercomponents/eased/.
Usage
import { useChat } from "@ai-sdk/react";import { Conversation } from "@/components/eased/conversation/conversation";import { Composer } from "@/components/eased/composer/composer";const { messages, status, sendMessage, stop } = useChat();<div className="flex h-full flex-col"> <Conversation messages={messages} status={status} /> <Composer status={status} onStop={stop} onSubmit={({ text }) => sendMessage({ text })} /></div>API reference
Conversation
| Prop | Type | Default | Description |
|---|---|---|---|
messages | ChatMessage[] | AI SDK UIMessages. | |
status | ChatStatus | "ready" | submitted shows a thinking line; streaming animates the last reply. |
renderMessage | (m, { streaming, position }) => ReactNode | Replace the default rendering. | |
empty | ReactNode | Shown with no messages. | |
hasEarlier / onLoadEarlier | "Load earlier" at the top; the view stays put as they prepend. | ||
thinkingLabel | string | "Thinking…" | |
contentClassName | string | p-(--pad) | Padding / max width of the column. |
Response
children (markdown string), isStreaming, components (override any element), className. Code fences render as Eased Code Blocks.
Keyboard
| Key | Action |
|---|---|
Tab | Focuses the log (then arrows / Page keys scroll it), then the jump pill when shown. |
Accessibility
- The list is
role="log"witharia-busywhile streaming, so screen readers announce a reply once it settles rather than word by word.
Motion
Following rides one spring that is retargeted per chunk, so bursts glide; any scroll that isn't the spring's own lets go. The jump pill rises 8px from 0.97 on the snappy spring and leaves faster; its label text-swaps to "New reply" when content lands while you're away. New words in a reply fade in from a 4px blur over 150ms on the enter curve. Reduced motion: scrolling jumps, the pill fades, words appear without blur.
Notes for AI
Pass useChat() straight in; tool parts render as Tool Call Rows automatically. Put the Composer below it in a flex column.
Do not add your own scroll-to-bottom effect or markdown renderer: both are built in.