Message
Chat message component with group, avatar, content, header and footer parts. Aligns to the start or end and reverses for outgoing messages; pairs with Bubble for the message body in chat UIs.
pnpm dlx shadcn add @01orbital/messageimport * as React from "react"
import { cn } from "cn"
function MessageGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="message-group"
className={cn("flex min-w-0 flex-col gap-2", className)}
{...props}
/>
)
}
function Message({
className,
align = "start",
...props
}: React.ComponentProps<"div"> & { align?: "start" | "end" }) {
return (
<div
data-slot="message"
data-align={align}
className={cn(
"group/message relative flex w-full min-w-0 gap-2 text-sm data-[align=end]:flex-row-reverse",
className
)}
{...props}
/>
)
}
function MessageAvatar({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="message-avatar"
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
)}
{...props}
/>
)
}
function MessageContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="message-content"
className={cn(
"flex w-full min-w-0 flex-col gap-2.5 wrap-break-word group-data-[align=end]/message:*:data-slot:self-end",
className
)}
{...props}
/>
)
}
function MessageHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="message-header"
className={cn(
"flex max-w-full min-w-0 items-center px-3.5 text-xs font-medium text-muted-foreground group-has-data-[variant=ghost]/message:px-0",
className
)}
{...props}
/>
)
}
function MessageFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="message-footer"
className={cn(
"flex max-w-full min-w-0 items-center px-3.5 text-xs font-medium text-muted-foreground group-has-data-[variant=ghost]/message:px-0 group-data-[align=end]/message:justify-end",
className
)}
{...props}
/>
)
}
export {
MessageGroup,
Message,
MessageAvatar,
MessageContent,
MessageFooter,
MessageHeader,
}
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import {
Bubble,
BubbleContent,
BubbleGroup,
BubbleReactions,
} from "@/components/ui/bubble"
import { Marker, MarkerContent } from "@/components/ui/marker"
import {
Message,
MessageAvatar,
MessageContent,
MessageFooter,
} from "@/components/ui/message"
// The copy: change text here, not in the markup.
const COPY = {
meAlt: "@me",
meInitials: "ME",
meDeploy: "Deploying to prod real quick.",
rabbitAlt: "@rabbit",
rabbitInitials: "R",
rabbitFriday: "It's 4:55 PM. On a Friday.",
meOneLine: "It's a one-line change.",
delivered: "Delivered",
rabbitAlways: "It's always a one-line change 😭.",
rabbitLook: "Alright, let me take a look.",
reaction: "Reactions: thumbs up",
typingName: "Oliver",
typing: " is typing...",
} as const
/** Message, after shadcn's example. */
export default function MessageDemo() {
return (
<div className="flex w-full max-w-sm flex-col gap-6 py-12">
<Message align="end">
<MessageAvatar>
<Avatar>
<AvatarImage src="/avatars/10.png" alt={COPY.meAlt} />
<AvatarFallback>{COPY.meInitials}</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble>
<BubbleContent>{COPY.meDeploy}</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message>
<MessageAvatar>
<Avatar>
<AvatarImage src="/avatars/02.png" alt={COPY.rabbitAlt} />
<AvatarFallback>{COPY.rabbitInitials}</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble variant="muted">
<BubbleContent>{COPY.rabbitFriday}</BubbleContent>
</Bubble>
</MessageContent>
</Message>
<Message align="end">
<MessageAvatar>
<Avatar>
<AvatarImage src="/avatars/10.png" alt={COPY.meAlt} />
<AvatarFallback>{COPY.meInitials}</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<Bubble>
<BubbleContent>{COPY.meOneLine}</BubbleContent>
</Bubble>
<MessageFooter>{COPY.delivered}</MessageFooter>
</MessageContent>
</Message>
<Message>
<MessageAvatar>
<Avatar>
<AvatarImage src="/avatars/02.png" alt={COPY.rabbitAlt} />
<AvatarFallback>{COPY.rabbitInitials}</AvatarFallback>
</Avatar>
</MessageAvatar>
<MessageContent>
<BubbleGroup>
<Bubble variant="muted">
<BubbleContent>{COPY.rabbitAlways}</BubbleContent>
</Bubble>
<Bubble variant="muted">
<BubbleContent>{COPY.rabbitLook}</BubbleContent>
<BubbleReactions aria-label={COPY.reaction}>
<span>👍</span>
</BubbleReactions>
</Bubble>
</BubbleGroup>
</MessageContent>
</Message>
<Marker role="status">
<MarkerContent className="shimmer">
<span className="font-medium">{COPY.typingName}</span>
{COPY.typing}
</MarkerContent>
</Marker>
</div>
)
}
Dependencies
npm: cn. The CLI installs them for you.
No other registry pieces.