Can you tell me a story? Maybe something about a magical forest?
Of course! I'd love to tell you a story about the Whispering Woods. Would you like to hear it?
Yes, please! I'm excited to hear it!
Deep in the heart of the Whispering Woods, there lived a young fox named Luna with fur as silver as moonlight. Unlike other foxes, Luna had the magical ability to speak with the ancient trees that surrounded her home.
One day, Luna discovered that the oldest tree in the forest had fallen silent. This was very unusual, as this particular oak tree loved telling stories about the forest's history. Concerned, Luna decided to investigate.
Oh no! What happened to the old oak tree?
As Luna approached the ancient oak, she noticed something glowing at its roots - a tiny crystal that pulsed with a soft blue light. The tree had been protecting this crystal for centuries, and now it was losing its power.
Luna knew she had to help. She gathered dewdrops from spider webs at dawn, collected starlight in flower petals at night, and asked the wind to share its oldest songs. With these magical ingredients, she restored the crystal's power.
Did it work? Did the old oak tree start speaking again?
Yes! The moment the crystal began glowing brightly again, the old oak's leaves rustled with joy, and its deep, wise voice returned. It thanked Luna for her help and shared even more wonderful stories about the forest's ancient magic.
From that day forward, Luna became known as the Guardian of the Whispering Woods, and she made sure to visit the old oak tree every day to hear its wonderful tales.
That was such a beautiful story! I loved how Luna helped save the old oak tree's voice.
I'm glad you enjoyed it! The story teaches us that even the smallest acts of kindness can help preserve the magic in our world.
"use client";
import type { UIMessage } from "@ai-sdk/react";
import {
	ChatMessage,
	ChatMessageAuthor,
	ChatMessageAvatar,
	ChatMessageAvatarFallback,
	ChatMessageAvatarImage,
	ChatMessageContainer,
	ChatMessageContent,
	ChatMessageHeader,
	ChatMessageMarkdown,
	ChatMessageTimestamp,
} from "@/components/ui/chat-message";
import {
	ChatMessageArea,
	ChatMessageAreaContent,
	ChatMessageAreaScrollButton,
} from "@/components/ui/chat-message-area";
const messages: UIMessage<{
	member: {
		image: string;
		name: string;
	};
}>[] = [
	{
		id: "1",
		parts: [
			{
				type: "text",
				text: "Can you tell me a story? Maybe something about a magical forest?",
			},
		],
		role: "user",
		metadata: {
			member: {
				image: "/avatar-1.png",
				name: "You",
			},
		},
	},
	{
		id: "2",
		parts: [
			{
				type: "text",
				text: "Of course! I'd love to tell you a story about the Whispering Woods. Would you like to hear it?",
			},
		],
		role: "assistant",
		metadata: {
			member: {
				image: "/avatar-2.png",
				name: "Assistant",
			},
		},
	},
	{
		id: "3",
		parts: [
			{
				type: "text",
				text: "Yes, please! I'm excited to hear it!",
			},
		],
		role: "user",
		metadata: {
			member: {
				image: "/avatar-1.png",
				name: "You",
			},
		},
	},
	{
		id: "4",
		parts: [
			{
				type: "text",
				text: "Deep in the heart of the Whispering Woods, there lived a young fox named Luna with fur as silver as moonlight. Unlike other foxes, Luna had the magical ability to speak with the ancient trees that surrounded her home.",
			},
		],
		role: "assistant",
		metadata: {
			member: {
				image: "/avatar-2.png",
				name: "Assistant",
			},
		},
	},
	{
		id: "5",
		parts: [
			{
				type: "text",
				text: "One day, Luna discovered that the oldest tree in the forest had fallen silent. This was very unusual, as this particular oak tree loved telling stories about the forest's history. Concerned, Luna decided to investigate.",
			},
		],
		role: "assistant",
		metadata: {
			member: {
				image: "/avatar-2.png",
				name: "Assistant",
			},
		},
	},
	{
		id: "6",
		parts: [
			{
				type: "text",
				text: "Oh no! What happened to the old oak tree?",
			},
		],
		role: "user",
		metadata: {
			member: {
				image: "/avatar-1.png",
				name: "You",
			},
		},
	},
	{
		id: "7",
		parts: [
			{
				type: "text",
				text: "As Luna approached the ancient oak, she noticed something glowing at its roots - a tiny crystal that pulsed with a soft blue light. The tree had been protecting this crystal for centuries, and now it was losing its power.",
			},
		],
		role: "assistant",
		metadata: {
			member: {
				image: "/avatar-2.png",
				name: "Assistant",
			},
		},
	},
	{
		id: "8",
		parts: [
			{
				type: "text",
				text: "Luna knew she had to help. She gathered dewdrops from spider webs at dawn, collected starlight in flower petals at night, and asked the wind to share its oldest songs. With these magical ingredients, she restored the crystal's power.",
			},
		],
		role: "assistant",
		metadata: {
			member: {
				image: "/avatar-2.png",
				name: "Assistant",
			},
		},
	},
	{
		id: "9",
		parts: [
			{
				type: "text",
				text: "Did it work? Did the old oak tree start speaking again?",
			},
		],
		role: "user",
		metadata: {
			member: {
				image: "/avatar-1.png",
				name: "You",
			},
		},
	},
	{
		id: "10",
		parts: [
			{
				type: "text",
				text: "Yes! The moment the crystal began glowing brightly again, the old oak's leaves rustled with joy, and its deep, wise voice returned. It thanked Luna for her help and shared even more wonderful stories about the forest's ancient magic.",
			},
		],
		role: "assistant",
		metadata: {
			member: {
				image: "/avatar-2.png",
				name: "Assistant",
			},
		},
	},
	{
		id: "11",
		parts: [
			{
				type: "text",
				text: "From that day forward, Luna became known as the Guardian of the Whispering Woods, and she made sure to visit the old oak tree every day to hear its wonderful tales.",
			},
		],
		role: "assistant",
		metadata: {
			member: {
				image: "/avatar-2.png",
				name: "Assistant",
			},
		},
	},
	{
		id: "12",
		parts: [
			{
				type: "text",
				text: "That was such a beautiful story! I loved how Luna helped save the old oak tree's voice.",
			},
		],
		role: "user",
		metadata: {
			member: {
				image: "/avatar-1.png",
				name: "You",
			},
		},
	},
	{
		id: "13",
		parts: [
			{
				type: "text",
				text: "I'm glad you enjoyed it! The story teaches us that even the smallest acts of kindness can help preserve the magic in our world.",
			},
		],
		role: "assistant",
		metadata: {
			member: {
				image: "/avatar-2.png",
				name: "Assistant",
			},
		},
	},
];
export function ChatMessageAreaDemo() {
	return (
		<ChatMessageArea>
			<ChatMessageAreaContent>
				{messages.map((message) => (
					<ChatMessage key={message.id}>
						<ChatMessageAvatar>
							<ChatMessageAvatarImage
								src={message.metadata?.member.image}
							/>
							<ChatMessageAvatarFallback>
								{message.metadata?.member.name
									.charAt(0)
									.toUpperCase()}
							</ChatMessageAvatarFallback>
						</ChatMessageAvatar>
						<ChatMessageContainer>
							<ChatMessageHeader>
								<ChatMessageAuthor>
									{message.metadata?.member.name}
								</ChatMessageAuthor>
								<ChatMessageTimestamp createdAt={new Date()} />
							</ChatMessageHeader>
							<ChatMessageContent>
								{message.parts
									.filter((part) => part.type === "text")
									.map((part) => (
										<ChatMessageMarkdown
											key={part.type}
											content={part.text}
										/>
									))}
							</ChatMessageContent>
						</ChatMessageContainer>
					</ChatMessage>
				))}
			</ChatMessageAreaContent>
			<ChatMessageAreaScrollButton />
		</ChatMessageArea>
	);
}
The Chat Message Area component provides a responsive and intelligent scrollable container specifically designed for streaming interfaces. It features a sophisticated auto-scrolling system that adapts to user interaction:
- Smart Auto-scroll: Automatically scrolls to new messages when they appear, but only if the user is already at the bottom
 - Interaction Awareness: Auto-scroll automatically disables when users scroll up to read previous messages
 - Re-engagement: Auto-scroll re-enables when users manually scroll back to the bottom
 - Mobile Optimized: On touch devices, auto-scroll pauses while the user's finger is on the screen
 - Convenient Navigation: Includes a scroll-to-bottom button that appears when not at the bottom
 
About
This component is built on top of use-stick-to-bottom, a React hook that provides smooth auto-scrolling functionality for chat interfaces and streaming content.
Installation
pnpm dlx shadcn@latest add @simple-ai/chat-message-area
Usage
import {
  ChatMessageArea,
  ChatMessageAreaContent,
  ChatMessageAreaScrollButton,
} from "@/components/ui/chat-message-area"<ChatMessageArea>
  <ChatMessageAreaContent className="px-4 py-8 space-y-4">
    {/* Your messages go here */}
  </ChatMessageAreaContent>
  <ChatMessageAreaScrollButton />
</ChatMessageArea>Examples
Alignment
You can align the scroll button to the left, right, or center using the alignment prop on ChatMessageAreaScrollButton.
Can you tell me a story? Maybe something about a magical forest?
Of course! I'd love to tell you a story about the Whispering Woods. Would you like to hear it?
Yes, please! I'm excited to hear it!
Deep in the heart of the Whispering Woods, there lived a young fox named Luna with fur as silver as moonlight. Unlike other foxes, Luna had the magical ability to speak with the ancient trees that surrounded her home.
One day, Luna discovered that the oldest tree in the forest had fallen silent. This was very unusual, as this particular oak tree loved telling stories about the forest's history. Concerned, Luna decided to investigate.
Oh no! What happened to the old oak tree?
As Luna approached the ancient oak, she noticed something glowing at its roots - a tiny crystal that pulsed with a soft blue light. The tree had been protecting this crystal for centuries, and now it was losing its power.
Luna knew she had to help. She gathered dewdrops from spider webs at dawn, collected starlight in flower petals at night, and asked the wind to share its oldest songs. With these magical ingredients, she restored the crystal's power.
Did it work? Did the old oak tree start speaking again?
Yes! The moment the crystal began glowing brightly again, the old oak's leaves rustled with joy, and its deep, wise voice returned. It thanked Luna for her help and shared even more wonderful stories about the forest's ancient magic.
From that day forward, Luna became known as the Guardian of the Whispering Woods, and she made sure to visit the old oak tree every day to hear its wonderful tales.
That was such a beautiful story! I loved how Luna helped save the old oak tree's voice.
I'm glad you enjoyed it! The story teaches us that even the smallest acts of kindness can help preserve the magic in our world.
"use client";
import type { UIMessage } from "@ai-sdk/react";
import {
	ChatMessage,
	ChatMessageAuthor,
	ChatMessageAvatar,
	ChatMessageAvatarFallback,
	ChatMessageAvatarImage,
	ChatMessageContainer,
	ChatMessageContent,
	ChatMessageHeader,
	ChatMessageMarkdown,
	ChatMessageTimestamp,
} from "@/components/ui/chat-message";
import {
	ChatMessageArea,
	ChatMessageAreaContent,
	ChatMessageAreaScrollButton,
} from "@/components/ui/chat-message-area";
const messages: UIMessage<{
	member: {
		image: string;
		name: string;
	};
}>[] = [
	{
		id: "1",
		parts: [
			{
				type: "text",
				text: "Can you tell me a story? Maybe something about a magical forest?",
			},
		],
		role: "user",
		metadata: {
			member: {
				image: "/avatar-1.png",
				name: "You",
			},
		},
	},
	{
		id: "2",
		parts: [
			{
				type: "text",
				text: "Of course! I'd love to tell you a story about the Whispering Woods. Would you like to hear it?",
			},
		],
		role: "assistant",
		metadata: {
			member: {
				image: "/avatar-2.png",
				name: "Assistant",
			},
		},
	},
	{
		id: "3",
		parts: [
			{
				type: "text",
				text: "Yes, please! I'm excited to hear it!",
			},
		],
		role: "user",
		metadata: {
			member: {
				image: "/avatar-1.png",
				name: "You",
			},
		},
	},
	{
		id: "4",
		parts: [
			{
				type: "text",
				text: "Deep in the heart of the Whispering Woods, there lived a young fox named Luna with fur as silver as moonlight. Unlike other foxes, Luna had the magical ability to speak with the ancient trees that surrounded her home.",
			},
		],
		role: "assistant",
		metadata: {
			member: {
				image: "/avatar-2.png",
				name: "Assistant",
			},
		},
	},
	{
		id: "5",
		parts: [
			{
				type: "text",
				text: "One day, Luna discovered that the oldest tree in the forest had fallen silent. This was very unusual, as this particular oak tree loved telling stories about the forest's history. Concerned, Luna decided to investigate.",
			},
		],
		role: "assistant",
		metadata: {
			member: {
				image: "/avatar-2.png",
				name: "Assistant",
			},
		},
	},
	{
		id: "6",
		parts: [
			{
				type: "text",
				text: "Oh no! What happened to the old oak tree?",
			},
		],
		role: "user",
		metadata: {
			member: {
				image: "/avatar-1.png",
				name: "You",
			},
		},
	},
	{
		id: "7",
		parts: [
			{
				type: "text",
				text: "As Luna approached the ancient oak, she noticed something glowing at its roots - a tiny crystal that pulsed with a soft blue light. The tree had been protecting this crystal for centuries, and now it was losing its power.",
			},
		],
		role: "assistant",
		metadata: {
			member: {
				image: "/avatar-2.png",
				name: "Assistant",
			},
		},
	},
	{
		id: "8",
		parts: [
			{
				type: "text",
				text: "Luna knew she had to help. She gathered dewdrops from spider webs at dawn, collected starlight in flower petals at night, and asked the wind to share its oldest songs. With these magical ingredients, she restored the crystal's power.",
			},
		],
		role: "assistant",
		metadata: {
			member: {
				image: "/avatar-2.png",
				name: "Assistant",
			},
		},
	},
	{
		id: "9",
		parts: [
			{
				type: "text",
				text: "Did it work? Did the old oak tree start speaking again?",
			},
		],
		role: "user",
		metadata: {
			member: {
				image: "/avatar-1.png",
				name: "You",
			},
		},
	},
	{
		id: "10",
		parts: [
			{
				type: "text",
				text: "Yes! The moment the crystal began glowing brightly again, the old oak's leaves rustled with joy, and its deep, wise voice returned. It thanked Luna for her help and shared even more wonderful stories about the forest's ancient magic.",
			},
		],
		role: "assistant",
		metadata: {
			member: {
				image: "/avatar-2.png",
				name: "Assistant",
			},
		},
	},
	{
		id: "11",
		parts: [
			{
				type: "text",
				text: "From that day forward, Luna became known as the Guardian of the Whispering Woods, and she made sure to visit the old oak tree every day to hear its wonderful tales.",
			},
		],
		role: "assistant",
		metadata: {
			member: {
				image: "/avatar-2.png",
				name: "Assistant",
			},
		},
	},
	{
		id: "12",
		parts: [
			{
				type: "text",
				text: "That was such a beautiful story! I loved how Luna helped save the old oak tree's voice.",
			},
		],
		role: "user",
		metadata: {
			member: {
				image: "/avatar-1.png",
				name: "You",
			},
		},
	},
	{
		id: "13",
		parts: [
			{
				type: "text",
				text: "I'm glad you enjoyed it! The story teaches us that even the smallest acts of kindness can help preserve the magic in our world.",
			},
		],
		role: "assistant",
		metadata: {
			member: {
				image: "/avatar-2.png",
				name: "Assistant",
			},
		},
	},
];
export function ChatMessageAreaDemoAlignment() {
	return (
		<ChatMessageArea>
			<ChatMessageAreaContent>
				{messages.map((message) => (
					<ChatMessage key={message.id}>
						<ChatMessageAvatar>
							<ChatMessageAvatarImage
								src={message.metadata?.member.image}
							/>
							<ChatMessageAvatarFallback>
								{message.metadata?.member.name
									.charAt(0)
									.toUpperCase()}
							</ChatMessageAvatarFallback>
						</ChatMessageAvatar>
						<ChatMessageContainer>
							<ChatMessageHeader>
								<ChatMessageAuthor>
									{message.metadata?.member.name}
								</ChatMessageAuthor>
								<ChatMessageTimestamp createdAt={new Date()} />
							</ChatMessageHeader>
							<ChatMessageContent>
								{message.parts
									.filter((part) => part.type === "text")
									.map((part) => (
										<ChatMessageMarkdown
											key={part.type}
											content={part.text}
										/>
									))}
							</ChatMessageContent>
						</ChatMessageContainer>
					</ChatMessage>
				))}
			</ChatMessageAreaContent>
			<ChatMessageAreaScrollButton alignment="center" />
		</ChatMessageArea>
	);
}
Streaming
Try out the auto-scrolling behavior in this live demo. Scroll up to pause auto-scrolling, scroll to bottom to re-enable it, or use the scroll button for quick navigation. On mobile, touch the screen to temporarily pause auto-scroll.
Can you tell me a magical story?
"use client";
import type { UIMessage } from "@ai-sdk/react";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import {
	ChatMessage,
	ChatMessageAuthor,
	ChatMessageAvatar,
	ChatMessageAvatarFallback,
	ChatMessageAvatarImage,
	ChatMessageContainer,
	ChatMessageContent,
	ChatMessageHeader,
	ChatMessageMarkdown,
	ChatMessageTimestamp,
} from "@/components/ui/chat-message";
import {
	ChatMessageArea,
	ChatMessageAreaContent,
	ChatMessageAreaScrollButton,
} from "@/components/ui/chat-message-area";
const userMessage: UIMessage<{
	member: {
		image: string;
		name: string;
	};
}> = {
	id: "1",
	parts: [
		{
			type: "text",
			text: "Can you tell me a magical story?",
		},
	],
	role: "user",
	metadata: {
		member: {
			image: "/avatar-1.png",
			name: "You",
		},
	},
};
const STORY = `# The Tale of the Enchanted Forest
Deep in a mystical realm, there lay an ancient forest that held secrets beyond imagination. The trees here weren't mere plants; they were the keepers of ancient wisdom, their leaves whispering tales of ages past.
## The Guardian's Call
In this magical place lived a young spirit named Aria, chosen by the forest itself to be its guardian. Her hair flowed like silver moonlight, and her eyes held the depth of ancient pools.
### The First Challenge
* One morning, Aria discovered that something was amiss
* The usually vibrant crystal flowers had begun to fade
* The ancient whispers of the trees grew fainter
* A shadow crept through the undergrowth
## The Journey Begins
Aria knew she had to act quickly. She gathered:
1. Dewdrops from spider webs at dawn
2. Starlight captured in moonflower petals
3. Songs of the morning birds
4. Tears of the evening mist
> "In the heart of every forest lies a truth waiting to be discovered" - Ancient Forest Proverb
### The Discovery
As Aria ventured deeper into the forest's heart, she found an ancient clearing. Here, the very air seemed to pulse with magic. Crystal formations emerged from the earth like frozen rainbows, each one containing memories of the forest's past.
## The Ancient Guardians
In her quest, Aria encountered the spirits of previous guardians:
* The Flame Keeper, whose torch lit the darkest paths
* The Wind Whisperer, who knew the language of storms
* The Earth Mother, who could heal the land with a touch
* The Star Walker, who mapped the celestial dance
Each spirit shared their wisdom:
1. "Change is the only constant in nature"
2. "Listen to the silence between heartbeats"
3. "Growth comes from embracing the unknown"
4. "Every ending is a new beginning"
### The Hidden Valley
Beyond the ancient clearing, Aria discovered a hidden valley where:
* Rainbow falls cascaded into pools of liquid starlight
* Flowers sang melodies of forgotten lullabies
* Butterflies painted the air with trails of golden dust
* Ancient runes danced on stone walls, telling stories of old
## The Dark Challenge
As night fell, shadows gathered and formed into creatures of doubt:
1. The Mist Wraith, who clouded clear thoughts
2. The Echo Thief, who stole confident voices
3. The Dream Weaver, who tangled hopes in fear
4. The Time Shifter, who made moments feel eternal
> "Courage is not the absence of fear, but the wisdom to dance with it" - Whispers of the Wise Trees
### The Battle Within
Aria faced her greatest challenge not in the physical realm, but within herself. She learned that:
* True power comes from acceptance
* Change can be beautiful and terrifying
* Magic flows through all living things
* Every creature has its own song to sing
## The Resolution
Through her connection with the forest, Aria learned that the fading magic was not a sign of decay, but of transformation. The forest wasn't dying; it was evolving, preparing for a new age of wonders.
### The New Beginning
* The crystal flowers bloomed again, brighter than ever
* New forms of magic emerged from the ancient earth
* The whispers of the trees grew stronger, carrying new songs
* And Aria, forever changed, became one with the forest's heart
## The Legacy
As the forest renewed itself:
1. Ancient spells transformed into new forms of magic
2. Lost pathways revealed themselves to worthy wanderers
3. The boundary between dreams and reality grew thin
4. Nature's wisdom found new ways to express itself
### The Eternal Dance
The forest continues its eternal dance:
* Seasons shift in kaleidoscope patterns
* Magic pulses in harmony with the earth's heartbeat
* New guardians arise when they're needed most
* Stories weave themselves into the fabric of reality
---
*And so, the tale of the Enchanted Forest continues, ever-changing, ever-growing, in the endless cycle of magic and wonder. Each day brings new mysteries, and each night holds its own enchantments, as the forest and its guardian dance their eternal dance of transformation and renewal...*`;
export function ChatMessageAreaDemo() {
	const [streamContent, setStreamContent] = useState("");
	const [isStreaming, setIsStreaming] = useState(false);
	useEffect(() => {
		if (!isStreaming) {
			return;
		}
		let currentIndex = 0;
		const words = STORY.split(" ");
		const streamInterval = setInterval(() => {
			if (currentIndex >= words.length) {
				clearInterval(streamInterval);
				setIsStreaming(false);
				return;
			}
			const nextChunk = words.slice(0, currentIndex + 3).join(" ");
			setStreamContent(nextChunk);
			currentIndex += 3;
		}, 70);
		return () => clearInterval(streamInterval);
	}, [isStreaming]);
	const handleStart = () => {
		setStreamContent("");
		setIsStreaming(true);
	};
	return (
		<div className="space-y-4 w-full h-full">
			<Button
				onClick={handleStart}
				disabled={isStreaming}
				className="w-full"
			>
				{streamContent ? "Restart Story" : "Start Story"}{" "}
				{isStreaming && "(Streaming...)"}
			</Button>
			<div className="border rounded-md h-[320px] overflow-y-auto">
				<ChatMessageArea>
					<ChatMessageAreaContent>
						<ChatMessage key={userMessage.id}>
							<ChatMessageAvatar>
								<ChatMessageAvatarImage
									src={userMessage.metadata?.member.image}
								/>
								<ChatMessageAvatarFallback>
									{userMessage.metadata?.member.name
										.charAt(0)
										.toUpperCase()}
								</ChatMessageAvatarFallback>
							</ChatMessageAvatar>
							<ChatMessageContainer>
								<ChatMessageHeader>
									<ChatMessageAuthor>
										{userMessage.metadata?.member.name}
									</ChatMessageAuthor>
									<ChatMessageTimestamp
										createdAt={new Date()}
									/>
								</ChatMessageHeader>
								<ChatMessageContent>
									{userMessage.parts
										.filter((part) => part.type === "text")
										.map((part) => (
											<ChatMessageMarkdown
												key={part.type}
												content={part.text}
											/>
										))}
								</ChatMessageContent>
							</ChatMessageContainer>
						</ChatMessage>
						{streamContent && (
							<ChatMessage key="2" id="2">
								<ChatMessageAvatar>
									<ChatMessageAvatarImage src="/avatar-2.png" />
									<ChatMessageAvatarFallback>
										A
									</ChatMessageAvatarFallback>
								</ChatMessageAvatar>
								<ChatMessageContainer>
									<ChatMessageHeader>
										<ChatMessageAuthor>
											Assistant
										</ChatMessageAuthor>
										<ChatMessageTimestamp
											createdAt={new Date()}
										/>
									</ChatMessageHeader>
									<ChatMessageContent>
										<ChatMessageMarkdown
											content={streamContent}
										/>
									</ChatMessageContent>
								</ChatMessageContainer>
							</ChatMessage>
						)}
					</ChatMessageAreaContent>
					<ChatMessageAreaScrollButton />
				</ChatMessageArea>
			</div>
		</div>
	);
}