v1.5

Chat Message

Layout for one user or assistant turn in an AI chat, with avatar, body, bubble, content and media slots.

Pro
Why does my Next.js build pass locally but fail on CI?
Your local build reuses the Turbo cache, so the workspace packages already exist. On CI the app builds before them. Add a ^build dependency to the build task and the order is fixed.

Description

ChatMessage is a compound component that lays out a single turn of a conversation. The root renders a <div role="article"> and takes from="assistant" (avatar column next to a body column) or from="user" (a stack pushed to the end edge). ChatMessage.Assistant and ChatMessage.User are the same root with from already set. The parts inside are plain elements you arrange yourself, so the component never owns your messages, transport or streaming state.

Use it for the message list of an assistant panel, a support chat or a full-page thread. ChatMessage.Content sets the one body text style for the turn, and Markdown inherits it, so a plain string and a parsed answer look the same. Actions, attachments and loaders from the other chat components drop into the body or under the bubble.

Don't use it for system notices inside a thread (rate limits, errors, model switches); use Inline Alert or Alert Banner. For comment threads with author names, timestamps and replies, build on Avatar and your own layout. For the scrolling container, stick-to-bottom behavior and the scroll button, wrap the messages in Chat Conversation.

ChatMessage is a Create UI Pro component. Install it with a Pro seat.

Installation

pnpm dlx @create-ui/cli add chat-message

Anatomy

<ChatMessage from="assistant">
  <ChatMessage.Avatar />
  <ChatMessage.Body>
    <ChatMessage.Content />
    <ChatMessage.Media />
  </ChatMessage.Body>
</ChatMessage>
 
<ChatMessage from="user">
  <ChatMessage.Media />
  <ChatMessage.Bubble>
    <ChatMessage.Content />
  </ChatMessage.Bubble>
</ChatMessage>

Usage

import { ChatMessage } from "@/components/ui/chat-message"
<ChatMessage.User>
  <ChatMessage.Bubble>
    <ChatMessage.Content>{message.text}</ChatMessage.Content>
  </ChatMessage.Bubble>
</ChatMessage.User>

Examples

Every example is a Pro preview (marked with a Pro badge).

Roles

Pass from straight from your message data (message.role in the Vercel AI SDK) to pick the layout. The children still differ per role: assistants get an avatar and a body, users get a bubble.

Pro
Can you shorten this release note title?
Sure. How about “Faster builds with remote caching”?
Perfect, ship it.

Avatar

ChatMessage.Avatar wraps Avatar. Give it src and fallback initials, or pass children for a brand glyph. It defaults to size="sm" (32px) and takes every Avatar size, shape and variant.

Pro
I pushed the pricing copy. Can you review it before the 3pm sync?
Without an image, the initials render through the fallback.
Children replace the image and initials, so a brand glyph can stand in for the assistant.

Without alt the avatar is decorative, because the message label already names the speaker. Add alt when the avatar identifies a real person, as in a shared thread.

Consecutive messages

Set show={false} on every avatar after the first in a run. It renders an empty spacer with the same footprint, so the follow-ups start at the same x position.

Pro
The migration finished on staging in 42 seconds.
Two tables still have rows without an owner, so the new foreign key is created as NOT VALID.
Run the backfill script first, then validate the constraint.

Long messages

A short bubble shrinks to its text. A long one stops at 80% of the column, and unbroken strings such as URLs wrap inside it instead of overflowing. The assistant body fills the column; add className="pe-12" to Body if you want a trailing gutter.

Pro
The CLI stops with an error when I add Chat Message Actions. I copied the command from https://createui.co/docs/components/chat-message-actions#installation
Chat Message Actions is a pro-only component, so the registry only serves its source to an account with a developer seat. Run login once on this machine, then run the same add command again. In CI, set the CREATEUI_TOKEN environment variable instead, because login needs a browser.
That worked, thanks!

Media

ChatMessage.Media stacks attachments. In a user turn put it above the bubble and align the group to the end; in an assistant turn put it inside Body, under the text.

Pro
create-banner.png
brand-guidelines.pdf
805 KB
Does the banner follow the brand guidelines?
Mostly. The logo needs more clear space on the left. I marked the spots in this copy.
banner-review.pdf
403 KB

Markdown

Content sets text-paragraph-sm and text-strongest. Markdown has no body size or color of its own, so paragraphs, lists and inline code pick up the same style as a plain string. Headings keep their own scale.

Pro
How do I make Turbo build my packages first?

Add the packages to the build graph so they compile first:

  1. Open turbo.json.
  2. Set dependsOn to ["^build"] on the build task.
  3. Clear the remote cache once.
json
{ "tasks": { "build": { "dependsOn": ["^build"] } } }

Streaming

Render Markdown with isStreaming inside Content while tokens arrive. Setting aria-busy on the message while it streams tells screen readers the turn is still changing.

Pro

With actions

Put ChatMessageActions inside Body for an assistant turn, or directly under the bubble for a user turn. The root carries the group/chat-message marker, so hovering anywhere on the message reveals the row.

Pro
What does the 409 from the billing API mean?
A 409 means the subscription changed since you loaded it. Fetch the latest version and retry the update with its etag.

Accessibility

KeyDescription
-Not focusable. Links, actions and code blocks inside the message keep their own keyboard behavior.

ARIA notes:

  • The root renders role="article" with an aria-label of "Assistant message" or "Your message", so each turn is announced with its speaker. Pass aria-label to translate it or name the sender ("Ayla Karagoz said").
  • Pass role to change the semantics, for example role="listitem" when the messages sit in a role="list" container. Chat Conversation renders role="log", which works with the default.
  • ChatMessage.Avatar is aria-hidden unless you pass alt. With alt it renders role="img" with that label, whether the image or the initials are showing.
  • The hidden-avatar spacer is always aria-hidden.
  • The message sets no aria-live. Announce finished responses from the conversation, not from every streamed token, and set aria-busy on a turn while it streams.

Styling

Tailwind override: pass className to any part. The classes merge with cn(), so text size and color tokens on Content replace the defaults.

<ChatMessage.User>
  <ChatMessage.Bubble className="bg-primary-base max-w-2/3">
    <ChatMessage.Content className="text-paragraph-xs text-static-white">
      {message.text}
    </ChatMessage.Content>
  </ChatMessage.Bubble>
</ChatMessage.User>

Data slots and attributes: the component sets these for CSS targeting:

  • data-slot="chat-message" on the root, with data-role ("assistant" or "user").
  • data-slot="avatar" on the avatar, which keeps the Avatar attributes data-size, data-shape, data-stroke and data-variant.
  • data-slot="chat-message-avatar-spacer" on the empty element rendered by show={false}.
  • data-slot="chat-message-body" on the assistant body column.
  • data-slot="chat-message-bubble" on the bubble, with data-role from the root.
  • data-slot="chat-message-content" on the text wrapper, with data-role from the root.
  • data-slot="chat-message-media" on the attachment stack.
[data-slot="chat-message-bubble"][data-role="user"] {
  /* ... */
}
  • Chat Conversation: the scrolling log around the messages, with stick-to-bottom and a scroll button.
  • Markdown: renders assistant text inside ChatMessage.Content and inherits its text style.
  • Chat Message Actions: copy, feedback and regenerate buttons under a message.
  • Chat Attachment: image and file tiles for ChatMessage.Media.
  • Chat Loader: the placeholder to show before the first token of a reply arrives.

API Reference

ChatMessage

The message root. Renders a <div>, provides the role to every part and extends React.ComponentProps<"div">. Also available as ChatMessage.Root and ChatMessageRoot.

Props

PropTypeDefaultDescription
roleReact.AriaRole"article"ARIA role of the message element.
aria-labelstring"Assistant message" / "Your message"Accessible name of the message. The default follows from.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Avatar and body for an assistant, bubble and media for a user.

Variants

VariantOptionsDefaultDescription
from"assistant" "user""assistant"assistant is a row with a 12px gap. user is a column aligned to the end edge with an 8px gap.

ChatMessage.Assistant / ChatMessage.User

The root with from fixed to "assistant" or "user". Take every ChatMessage prop except from. Also exported as ChatMessageAssistant and ChatMessageUser.

Props

PropTypeDefaultDescription
roleReact.AriaRole"article"ARIA role of the message element.
aria-labelstring"Assistant message" / "Your message"Accessible name of the message.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-The message parts.

ChatMessage.Avatar

The speaker avatar, top-aligned in the row. Wraps Avatar, so it extends the Avatar props (every <div> attribute plus size, shape, variant, background and stroke). Also exported as ChatMessageAvatar.

Props

PropTypeDefaultDescription
srcstring-Image source, rendered through AvatarImage.
fallbackReact.ReactNode-Initials rendered through AvatarText until the image loads, or when there is none.
altstring-Accessible name. Without it the avatar is aria-hidden.
showbooleantrueWhen false, renders an empty spacer with the same footprint instead.
strokebooleantrueDraws the Avatar outline.
backgroundbooleantrueFills the Avatar background.
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Replaces the image and fallback, for example with an icon.

Variants

VariantOptionsDefaultDescription
size"2xs" "xs" "sm" "md" "lg" "xl" "2xl""sm"20px to 64px. The spacer uses the same size.
shape"circle" "rounded""circle"Corner radius, passed to Avatar.
variantany Avatar variant, such as "gradient-indigo"-Color treatment, passed to Avatar.

ChatMessage.Body

The assistant column next to the avatar. A flex column with an 8px gap that takes the remaining width and can shrink below its content. Extends React.ComponentProps<"div">. Also exported as ChatMessageBody.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Content, media, actions and tool output.

ChatMessage.Bubble

The user bubble: bg-weak, 16px radius, shrinks to its text and caps at 80% of the column. Extends React.ComponentProps<"div">. Also exported as ChatMessageBubble.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Usually a ChatMessage.Content.

ChatMessage.Content

The text wrapper. Sets text-paragraph-sm text-strongest and wraps long words, for plain text and Markdown alike. Extends React.ComponentProps<"div">. Also exported as ChatMessageContent.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-Text or a Markdown element.

ChatMessage.Media

A flex column with an 8px gap for attachments. Extends React.ComponentProps<"div">. Also exported as ChatMessageMedia.

Props

PropTypeDefaultDescription
classNamestring-Tailwind classes merged via cn().
childrenReact.ReactNode-ChatAttachment tiles or a ChatAttachmentGroup.

useChatMessageContext

Returns { role } from the nearest message root, or null outside one. Use it in custom parts that style themselves per role.

Types

type ChatMessageRole = "assistant" | "user"
type ChatMessageContextValue = { role: ChatMessageRole }

chatMessageVariants (the root recipe, with the from variant) and chatMessageContentVariants (the content text style) are exported for composing your own parts.