@cf/ui

@cf/message

Message

The row in a chat thread that holds one turn, aligned to the start or end, with optional avatar, header and footer around its bubbles.

What are you working on at the moment?
Delivered
CF
Connor
Pulling the site’s components out into a registry, with rules an agent can read.

Layout only: no state, no Base UI. Message is a flex row that reverses for align="end", and MessageContent pushes its slotted children to the end on those rows. MessageAvatar lifts itself above a footer when there is one.

In the chat panel every turn is Message then MessageContent then a Bubble, with a MessageFooter for the Sent and Delivered receipt. I don't use MessageAvatar or MessageHeader there yet.

import { Message, MessageContent, MessageFooter } from "@/components/ui/message";
import { Bubble, BubbleContent } from "@/components/ui/bubble";
 
<Message align="end">
  <MessageContent>
    <Bubble align="end" variant="outline">
      <BubbleContent>{text}</BubbleContent>
    </Bubble>
    <MessageFooter>Delivered</MessageFooter>
  </MessageContent>
</Message>

Props

PropTypeDefault
Message alignstart | endstart

Rules

The same rules ship in DESIGN.md and the MCP server, for the agents building with this.

  • must

    Set align="end" on Message for the person's own turns and leave align="start" for everyone else, and set the same align on the Bubble inside.

    align-by-speaker

  • must

    Put bubbles, quotes and attachments inside MessageContent, not directly in Message, so they stack and follow the row's alignment.

    content-wraps-bubbles

  • should

    Put timestamps and delivery receipts in MessageFooter, which right-aligns itself on end messages and drops its padding next to a ghost bubble.

    footer-for-receipts

  • should

    Wrap each Message in a MessageScrollerItem when it's inside a MessageScroller, rather than the other way round.

    scroller-item-outside

Install

bunx shadcn@latest add @cf/message

Needs the @cf registry in your components.json once. The theme and any sibling components come along automatically.

Source

message.tsxShow
import { cn } from "cn";
import type * as React from "react";
 
function MessageGroup({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      className={cn("flex min-w-0 flex-col gap-2", className)}
      data-slot="message-group"
      {...props}
    />
  );
}
 
function Message({
  className,
  align = "start",
  ...props
}: React.ComponentProps<"div"> & { align?: "start" | "end" }) {
  return (
    <div
      className={cn(
        "group/message relative flex w-full min-w-0 gap-2 text-sm data-[align=end]:flex-row-reverse",
        className
      )}
      data-align={align}
      data-slot="message"
      {...props}
    />
  );
}
 
function MessageAvatar({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      className={cn(
        "flex w-fit min-w-8 shrink-0 items-center justify-center self-end overflow-hidden rounded-full bg-muted group-has-data-[slot=message-footer]/message:-translate-y-8",
        className
      )}
      data-slot="message-avatar"
      {...props}
    />
  );
}
 
function MessageContent({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      className={cn(
        "wrap-break-word flex w-full min-w-0 flex-col gap-2.5 group-data-[align=end]/message:*:data-slot:self-end",
        className
      )}
      data-slot="message-content"
      {...props}
    />
  );
}
 
function MessageHeader({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      className={cn(
        "flex min-w-0 max-w-full items-center px-3 font-medium text-muted-foreground text-xs group-has-data-[variant=ghost]/message:px-0",
        className
      )}
      data-slot="message-header"
      {...props}
    />
  );
}
 
function MessageFooter({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      className={cn(
        "flex min-w-0 max-w-full items-center px-3 font-medium text-muted-foreground text-xs group-has-data-[variant=ghost]/message:px-0 group-data-[align=end]/message:justify-end",
        className
      )}
      data-slot="message-footer"
      {...props}
    />
  );
}
 
export {
  Message,
  MessageAvatar,
  MessageContent,
  MessageFooter,
  MessageGroup,
  MessageHeader,
};