Message
Displays a chat interface message from either a user or an AI.
The Message component displays a chat interface message from either a user or an AI. It includes an avatar, a name, and a message content.
"use client";import { Message, MessageAvatar, MessageContent } from "@/components/ai-elements/elements/message";import { nanoid } from "nanoid";const messages: { key: string; from: "user" | "assistant"; content: string; avatar: string; name: string;}[] = [ { key: nanoid(), from: "user", content: "Hello, how are you?", avatar: "https://github.com/haydenbleasel.png", name: "Hayden Bleasel", },];const Example = () => ( <> {messages.map(({ content, ...message }) => ( <Message from={message.from} key={message.key}> <MessageContent>{content}</MessageContent> <MessageAvatar name={message.name} src={message.avatar} /> </Message> ))} </>);export default Example;Installation
npx ai-elements@latest add messagenpx shadcn@latest add @ai-elements/messageimport { Avatar, AvatarFallback, AvatarImage,} from "@repo/shadcn-ui/components/ui/avatar";import { cn } from "@repo/shadcn-ui/lib/utils";import type { UIMessage } from "ai";import { cva, type VariantProps } from "class-variance-authority";import type { ComponentProps, HTMLAttributes } from "react";export type MessageProps = HTMLAttributes<HTMLDivElement> & { from: UIMessage["role"];};export const Message = ({ className, from, ...props }: MessageProps) => ( <div className={cn( "group flex w-full items-end justify-end gap-2 py-4", from === "user" ? "is-user" : "is-assistant flex-row-reverse justify-end", className )} {...props} />);const messageContentVariants = cva( "is-user:dark flex flex-col gap-2 overflow-hidden rounded-lg text-sm", { variants: { variant: { contained: [ "max-w-[80%] px-4 py-3", "group-[.is-user]:bg-primary group-[.is-user]:text-primary-foreground", "group-[.is-assistant]:bg-secondary group-[.is-assistant]:text-foreground", ], flat: [ "group-[.is-user]:max-w-[80%] group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground", "group-[.is-assistant]:text-foreground", ], }, }, defaultVariants: { variant: "contained", }, });export type MessageContentProps = HTMLAttributes<HTMLDivElement> & VariantProps<typeof messageContentVariants>;export const MessageContent = ({ children, className, variant, ...props}: MessageContentProps) => ( <div className={cn(messageContentVariants({ variant, className }))} {...props} > {children} </div>);export type MessageAvatarProps = ComponentProps<typeof Avatar> & { src: string; name?: string;};export const MessageAvatar = ({ src, name, className, ...props}: MessageAvatarProps) => ( <Avatar className={cn("size-8 ring-1 ring-border", className)} {...props}> <AvatarImage alt="" className="mt-0 mb-0" src={src} /> <AvatarFallback>{name?.slice(0, 2) || "ME"}</AvatarFallback> </Avatar>);Usage
import { Message, MessageContent } from '@/components/ai-elements/message';// Default contained variant
<Message from="user">
<MessageContent>Hi there!</MessageContent>
</Message>
// Flat variant for a minimalist look
<Message from="assistant">
<MessageContent variant="flat">Hello! How can I help you today?</MessageContent>
</Message>Usage with AI SDK
Render messages in a list with useChat.
Add the following component to your frontend:
'use client';
import { Message, MessageContent } from '@/components/ai-elements/message';
import { useChat } from '@ai-sdk/react';
import { Response } from '@/components/ai-elements/response';
const MessageDemo = () => {
const { messages } = useChat();
return (
<div className="max-w-4xl mx-auto p-6 relative size-full rounded-lg border h-[600px]">
<div className="flex flex-col h-full">
{messages.map((message) => (
<Message from={message.role} key={message.id}>
<MessageContent>
{message.parts.map((part, i) => {
switch (part.type) {
case 'text': // we don't use any reasoning or tool calls in this example
return (
<Response key={`${message.id}-${i}`}>
{part.text}
</Response>
);
default:
return null;
}
})}
</MessageContent>
</Message>
))}
</div>
</div>
);
};
export default MessageDemo;Features
- Displays messages from both the user and AI assistant with distinct styling.
- Two visual variants: contained (default) and flat for different design preferences.
- Includes avatar images for message senders with fallback initials.
- Shows the sender's name through avatar fallbacks.
- Automatically aligns user and assistant messages on opposite sides.
- Uses different background colors for user and assistant messages.
- Accepts any React node as message content.
Variants
Contained (default)
The contained variant provides distinct visual separation with colored backgrounds:
- User messages appear with primary background color and are right-aligned
- Assistant messages have secondary background color and are left-aligned
- Both message types have padding and rounded corners
Flat
The flat variant offers a minimalist design that matches modern AI interfaces like ChatGPT and Gemini:
- User messages use softer secondary colors with subtle borders
- Assistant messages display full-width without background or padding
- Creates a cleaner, more streamlined conversation appearance
Notes
Always render the AIMessageContent first, then the AIMessageAvatar. The AIMessage component is a wrapper that determines the alignment of the message.
Examples
Render Markdown
We can use the Response component to render markdown content.
What is the weather in Tokyo?
"use client";import { Message, MessageAvatar, MessageContent } from "@/components/ai-elements/elements/message";import { Response } from "@/components/ai-elements/elements/response";import { useEffect, useState } from "react";const assistantMessageTokens = [ "To", " get", " the", " **", "weather", " in", " Tokyo", "**", " using", " an", " API", " call", ",", " you", " can", " use", " the", " [", "OpenWeatherMap", "](", "https://openweathermap.org/api", ")", " API", ".", " After", " signing", " up", ",", " you", " can", " make", " a", " request", " to", " their", " API", ":", "\n\n", "```", "bash", "\n", "curl", " -X", " GET", ' "https://api.openweathermap.org/data/2.5/weather?q=Tokyo&appid=YOUR_API_KEY"', " \\", "\n", " --header", ' "Content-Type:', ' application/json"', " \\", "\n", " --header", ' "Accept:', ' application/json"', "\n", "```", "\n\n", "This", " will", " return", " a", " JSON", " object", " with", " the", " weather", " data", " for", " Tokyo", ".",];const Example = () => { const [content, setContent] = useState(""); useEffect(() => { let currentContent = ""; let index = 0; const interval = setInterval(() => { if (index < assistantMessageTokens.length) { currentContent += assistantMessageTokens[index]; setContent(currentContent); index++; } else { clearInterval(interval); } }, 100); return () => clearInterval(interval); }, []); return ( <> <Message from="user"> <MessageContent> <Response>What is the weather in Tokyo?</Response> </MessageContent> <MessageAvatar name="Hayden Bleasel" src="https://github.com/haydenbleasel.png" /> </Message> <Message from="assistant"> <MessageContent> <Response>{content}</Response> </MessageContent> <MessageAvatar name="OpenAI" src="https://github.com/openai.png" /> </Message> </> );};export default Example;Flat Variant
The flat variant provides a minimalist design that matches modern AI interfaces.
"use client";import { Message, MessageContent } from "@/components/ai-elements/elements/message";import { nanoid } from "nanoid";const messages: { key: string; from: "user" | "assistant"; content: string; avatar: string; name: string;}[] = [ { key: nanoid(), from: "user", content: "Can you explain what the flat variant does?", avatar: "https://github.com/haydenbleasel.png", name: "Hayden Bleasel", }, { key: nanoid(), from: "assistant", content: "The flat variant provides a minimalist design for messages. User messages appear with subtle secondary colors and borders, while assistant messages display full-width without background padding. This creates a cleaner, more modern conversation interface similar to ChatGPT and other contemporary AI assistants.", avatar: "https://github.com/anthropics.png", name: "Claude", }, { key: nanoid(), from: "user", content: "That looks much cleaner! I like how it matches modern AI interfaces.", avatar: "https://github.com/haydenbleasel.png", name: "Hayden Bleasel", }, { key: nanoid(), from: "assistant", content: "Exactly! The flat variant is perfect when you want a more streamlined appearance that puts focus on the conversation content rather than visual containers. It works especially well in full-width layouts and professional applications.", avatar: "https://github.com/anthropics.png", name: "Claude", },];const Example = () => ( <div className="space-y-2"> {messages.map(({ content, ...message }) => ( <Message from={message.from} key={message.key}> <MessageContent variant="flat">{content}</MessageContent> </Message> ))} </div>);export default Example;Props
<Message />
Prop
Type
<MessageContent />
Prop
Type
<MessageAvatar />
Prop
Type