Start a conversation
This is a simulated conversation
"use client"
import { useEffect, useState } from "react"
import { Card } from "@/components/ui/card"
import {
Conversation,
ConversationContent,
ConversationEmptyState,
ConversationScrollButton,
} from "@/components/ui/conversation"
import { Message, MessageContent } from "@/components/ui/message"
import { Orb } from "@/components/ui/orb"
import { Response } from "@/components/ui/response"
import { ShimmeringText } from "@/components/ui/shimmering-text"
const allMessages = [
{
id: "1",
role: "user" as const,
parts: [
{
type: "text",
text: "Hey, I need help with my order",
},
],
},
{
id: "2",
role: "assistant" as const,
parts: [
{
type: "text",
tokens: [
"Hi!",
" I'd",
" be",
" happy",
" to",
" help",
" you",
" with",
" your",
" order.",
" Could",
" you",
" please",
" provide",
" your",
" order",
" number?",
],
text: "Hi! I'd be happy to help you with your order. Could you please provide your order number?",
},
],
},
{
id: "3",
role: "user" as const,
parts: [
{
type: "text",
text: "It's ORDER-12345",
},
],
},
{
id: "4",
role: "assistant" as const,
parts: [
{
type: "text",
tokens: [
"Thank",
" you!",
" Let",
" me",
" pull",
" up",
" your",
" order",
" details.",
" I",
" can",
" see",
" that",
" your",
" order",
" was",
" placed",
" on",
" March",
" 15th",
" and",
" is",
" currently",
" being",
" processed.",
" It",
" should",
" ship",
" within",
" the",
" next",
" 1-2",
" business",
" days.",
" Is",
" there",
" anything",
" specific",
" you'd",
" like",
" to",
" know",
" about",
" this",
" order?",
],
text: "Thank you! Let me pull up your order details. I can see that your order was placed on March 15th and is currently being processed. It should ship within the next 1-2 business days. Is there anything specific you'd like to know about this order?",
},
],
},
{
id: "5",
role: "user" as const,
parts: [
{
type: "text",
text: "Can I change the shipping address?",
},
],
},
{
id: "6",
role: "assistant" as const,
parts: [
{
type: "text",
tokens: [
"Absolutely!",
" Since",
" the",
" order",
" hasn't",
" shipped",
" yet,",
" I",
" can",
" update",
" the",
" shipping",
" address",
" for",
" you.",
" What",
" would",
" you",
" like",
" the",
" new",
" address",
" to",
" be?",
],
text: "Absolutely! Since the order hasn't shipped yet, I can update the shipping address for you. What would you like the new address to be?",
},
],
},
]
const ConversationDemo = () => {
const [messages, setMessages] = useState<typeof allMessages>([])
const [streamingMessageIndex, setStreamingMessageIndex] = useState<
number | null
>(null)
const [streamingContent, setStreamingContent] = useState("")
useEffect(() => {
const timeouts: NodeJS.Timeout[] = []
const intervals: NodeJS.Timeout[] = []
let currentMessageIndex = 0
const addNextMessage = () => {
if (currentMessageIndex >= allMessages.length) return
const message = allMessages[currentMessageIndex]
const part = message.parts[0]
if (message.role === "assistant" && "tokens" in part && part.tokens) {
setStreamingMessageIndex(currentMessageIndex)
setStreamingContent("")
let currentContent = ""
let tokenIndex = 0
const streamInterval = setInterval(() => {
if (tokenIndex < part.tokens.length) {
currentContent += part.tokens[tokenIndex]
setStreamingContent(currentContent)
tokenIndex++
} else {
clearInterval(streamInterval)
setMessages((prev) => [...prev, message])
setStreamingMessageIndex(null)
setStreamingContent("")
currentMessageIndex++
// Add next message after a delay
timeouts.push(setTimeout(addNextMessage, 500))
}
}, 100)
intervals.push(streamInterval)
} else {
setMessages((prev) => [...prev, message])
currentMessageIndex++
timeouts.push(setTimeout(addNextMessage, 800))
}
}
// Start after 1 second
timeouts.push(setTimeout(addNextMessage, 1000))
return () => {
timeouts.forEach((timeout) => clearTimeout(timeout))
intervals.forEach((interval) => clearInterval(interval))
}
}, [])
return (
<Card className="relative mx-auto my-0 size-full h-[400px] py-0">
<div className="flex h-full flex-col">
<Conversation>
<ConversationContent>
{messages.length === 0 && streamingMessageIndex === null ? (
<ConversationEmptyState
icon={<Orb className="size-12" />}
title="Start a conversation"
description="This is a simulated conversation"
/>
) : (
<>
{messages.map((message) => (
<Message from={message.role} key={message.id}>
<MessageContent>
{message.parts.map((part, i) => {
switch (part.type) {
case "text":
return (
<Response key={`${message.id}-${i}`}>
{part.text}
</Response>
)
default:
return null
}
})}
</MessageContent>
{message.role === "assistant" && (
<div className="ring-border size-8 overflow-hidden rounded-full ring-1">
<Orb className="h-full w-full" agentState={null} />
</div>
)}
</Message>
))}
{streamingMessageIndex !== null && (
<Message
from={allMessages[streamingMessageIndex].role}
key={`streaming-${streamingMessageIndex}`}
>
<MessageContent>
<Response>{streamingContent || "\u200B"}</Response>
</MessageContent>
{allMessages[streamingMessageIndex].role ===
"assistant" && (
<div className="ring-border size-8 overflow-hidden rounded-full ring-1">
<Orb className="h-full w-full" agentState="talking" />
</div>
)}
</Message>
)}
</>
)}
</ConversationContent>
<ConversationScrollButton />
</Conversation>
</div>
</Card>
)
}
export ConversationDemo
Installation
pnpm dlx @elevenlabs/agents-cli@latest components add conversation
Usage
import {
Conversation,
ConversationContent,
ConversationEmptyState,
ConversationScrollButton,
} from "@/components/ui/conversation"
Basic Conversation
<Conversation>
<ConversationContent>
{messages.map((message) => (
<div key={message.id}>{message.content}</div>
))}
</ConversationContent>
<ConversationScrollButton />
</Conversation>
With Empty State
<Conversation>
<ConversationContent>
{messages.length === 0 ? (
<ConversationEmptyState
title="No messages yet"
description="Start a conversation to see messages here"
/>
) : (
messages.map((message) => <div key={message.id}>{message.content}</div>)
)}
</ConversationContent>
<ConversationScrollButton />
</Conversation>
API Reference
Conversation
The main container component that manages scrolling behavior and sticky-to-bottom functionality.
Props
Extends all props from StickToBottom
component from use-stick-to-bottom
.
Prop | Type | Description |
---|---|---|
className | string | Optional CSS classes |
initial | "smooth" | Initial scroll behavior (default: "smooth") |
resize | "smooth" | Resize scroll behavior (default: "smooth") |
...props | StickToBottom | All standard StickToBottom component props |
ConversationContent
Container for conversation messages.
Props
Prop | Type | Description |
---|---|---|
className | string | Optional CSS classes |
...props | StickToBottom.Content | All StickToBottom.Content props |
ConversationEmptyState
Displays when there are no messages in the conversation.
Props
Prop | Type | Description |
---|---|---|
title | string | Title text (default: "No messages yet") |
description | string | Description text |
icon | ReactNode | Optional icon to display |
className | string | Optional CSS classes |
children | ReactNode | Custom content (overrides default rendering) |
...props | HTMLDivElement | All standard div element props |
ConversationScrollButton
A scroll-to-bottom button that appears when the user scrolls up.
Props
Prop | Type | Description |
---|---|---|
className | string | Optional CSS classes |
...props | ButtonProps | All standard Button props |
Notes
- Built on top of
use-stick-to-bottom
for smooth scrolling behavior - Automatically scrolls to bottom when new messages are added
- Scroll button only appears when user has scrolled away from the bottom
- Supports smooth scrolling animations
- Works with any message component structure
- This component is inspired by Vercel's AI SDK Conversation component with modifications for ElevenLabs UI
Deploy and Scale Agents with ElevenLabs
ElevenLabs delivers the infrastructure and developer experience you need to ship reliable audio & agent applications at scale.
Talk to an expert