Components / Chat
Message
A chat row and what it can carry: bubble fills, a link preview, a file, a sticker, a time and reactions.
import {
Message,
MessageAvatar,
MessageBubble,
MessageBubbleContent,
MessageContent,
MessageFile,
MessageFooter,
MessageGroup,
MessageHeader,
MessageLink,
MessageReactions,
MessageSticker,
MessageTime,
} from "@/components/ui/hiip-ui/message";
// Placeholder marks, not brand assets: every image and icon in these parts is a
// prop, so the demo supplies plain shapes rather than shipping someone's logo
// inside a registry payload.
function PlayPlate() {
return (
<div className="flex aspect-video w-full items-center justify-center bg-foreground">
<svg viewBox="0 0 24 24" className="size-10 fill-background" role="img">
<title>Video</title>
<path d="M8 5v14l11-7z" />
</svg>
</div>
);
}
function DocIcon() {
return (
<svg viewBox="0 0 24 24" className="fill-primary" role="img">
<title>Document</title>
<path d="M6 2h8l4 4v16H6z" opacity="0.25" />
<path d="M8 12h8v1.5H8zm0 3h8v1.5H8zm0 3h5v1.5H8z" />
</svg>
);
}
function MessageDemo() {
return (
<MessageGroup className="w-full max-w-sm">
{/* `variant` on the ROW, not on each bubble: one author, one fill, and a
grouped run that cannot drift message by message. */}
<Message variant="received">
<MessageAvatar className="size-8 text-xs">CR</MessageAvatar>
<MessageContent>
{/* Name then time, written once. The header mirrors itself on the
outgoing row below, so the same order reads correctly on both. */}
<MessageHeader>
Creator
<span className="font-normal text-muted-foreground">
18 days ago
</span>
</MessageHeader>
<MessageBubble>
<MessageBubbleContent>
Can I still join the campaign?
</MessageBubbleContent>
</MessageBubble>
{/* A grouped run: one author line, and only the LAST bubble carries
a time. */}
<MessageBubble>
<MessageBubbleContent>
Mình cf nha
<MessageTime dateTime="2026-08-27">18 days ago</MessageTime>
</MessageBubbleContent>
{/* No `align`: it follows the row, so the badge hangs on the side
facing the thread rather than out at the margin. */}
<MessageReactions>❤️</MessageReactions>
</MessageBubble>
</MessageContent>
</Message>
<Message align="end" variant="sent">
<MessageAvatar className="size-8 bg-primary text-xs text-primary-foreground">
OP
</MessageAvatar>
<MessageContent>
<MessageHeader>
Operator
<span className="font-normal text-muted-foreground">
18 days ago
</span>
</MessageHeader>
<MessageBubble>
<MessageBubbleContent>
You can — briefing is open until Friday.
</MessageBubbleContent>
</MessageBubble>
<MessageBubble>
<MessageBubbleContent>
<MessageFile
icon={<DocIcon />}
name="sample-document-with-a-long-filename.pdf"
meta="PDF · 1004.3 KB"
action={
<button
type="button"
aria-label="Download sample-document-with-a-long-filename.pdf"
className="flex size-9 items-center justify-center rounded-full bg-foreground text-background"
>
<svg
viewBox="0 0 24 24"
className="size-4 fill-current"
role="img"
>
<title>Download</title>
<path d="M12 3v10.6l3.3-3.3 1.4 1.4L12 17.4l-4.7-5.7 1.4-1.4 3.3 3.3V3zM5 19h14v2H5z" />
</svg>
</button>
}
/>
<MessageTime dateTime="2026-09-08">6 days ago</MessageTime>
</MessageBubbleContent>
</MessageBubble>
{/* The footer is the STATUS slot, now that the timestamp lives inside
the bubble. Two different facts, so two different places. */}
<MessageFooter>Seen</MessageFooter>
</MessageContent>
</Message>
{/* No avatar and no header — a row is still a row without them. */}
<Message variant="received">
<MessageContent>
<MessageBubble>
<MessageBubbleContent>
<MessageLink
href="https://example.com/media/0000000000000000000"
media={<PlayPlate />}
site="example.com"
title="Watch the campaign cut"
description="A 30-second edit for the brand channel, ready to re-upload."
/>
</MessageBubbleContent>
</MessageBubble>
</MessageContent>
</Message>
{/* A lone bubble in the row: `align="end"` reverses the row, so it packs
right with no content column to put it there. */}
<Message align="end">
<MessageBubble>
<MessageSticker>
<span className="block p-4 text-5xl leading-none">🎉</span>
<MessageTime dateTime="2026-08-14">a month ago</MessageTime>
</MessageSticker>
</MessageBubble>
</Message>
</MessageGroup>
);
}Install — needs @hiip registered in components.json
bunx shadcn@latest add @hiip/messageOr by URL, with the token in the query
bunx shadcn@latest add "https://ui.hiip.ai/r/message.json?token=$HIIP_REGISTRY_TOKEN"Both need a token — set one up.
Anatomy
Import from components/ui/hiip-ui/message.
<MessageGroup>
{/* `variant` is the row's, so a grouped run cannot drift bubble by bubble */}
<Message align="start" variant="received">
<MessageAvatar />
<MessageContent>
<MessageHeader />
{/* one or more, each wearing the row's variant */}
<MessageBubble>
<MessageBubbleContent>
{/* text, or one of the parts below */}
<MessageLink href="…" media={…} site="…" title="…" description="…" />
<MessageFile icon={…} name="…" meta="…" action={…} />
<MessageTime dateTime="…">…</MessageTime>
</MessageBubbleContent>
<MessageReactions>👍</MessageReactions>
</MessageBubble>
<MessageBubble>
<MessageSticker>
{/* a sticker or emoji, instead of MessageBubbleContent */}
</MessageSticker>
</MessageBubble>
<MessageFooter />
</MessageContent>
</Message>
</MessageGroup>API Reference
Generated from the source. Props forwarded to the underlying element are not listed.
MessageGroup
No props of its own.
Message
| Prop | Type | Default |
|---|---|---|
align | "end" | "start" | undefined | "start" |
variantThe fill every bubble in this row wears. | MessageVariant | undefined | "default" |
MessageAvatar
No props of its own.
MessageContent
No props of its own.
MessageHeader
No props of its own.
MessageFooter
No props of its own.
MessageBubble
One bubble in a row.
| Prop | Type | Default |
|---|---|---|
variantOverrides the row's variant. | MessageVariant | undefined | — |
MessageBubbleContent
| Prop | Type | Default |
|---|---|---|
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component. | ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, {}> | undefined | — |
MessageReactions
| Prop | Type | Default |
|---|---|---|
align`auto` follows the row's own side. | "auto" | "end" | "start" | undefined | "auto" |
side | "top" | "bottom" | undefined | "bottom" |
MessageTime
The in-bubble timestamp: the last message of a group carries its own time INSIDE the fill, rather than pushing another line under the thread.
No props of its own.
MessageLink
A link preview: an optional media plate, the raw URL, then the site's own metadata.
| Prop | Type | Default |
|---|---|---|
description | ReactNode | — |
iconThe favicon or platform mark beside the title. | ReactNode | — |
mediaFull-bleed plate above the URL — a thumbnail, a player, a placeholder. | ReactNode | — |
site | ReactNode | — |
title | ReactNode | — |
MessageFile
An attachment row: icon tile, name and size, and whatever the caller wants the trailing control to be.
| Prop | Type | Default |
|---|---|---|
namerequired | ReactNode | — |
action | ReactNode | — |
icon | ReactNode | — |
metaType and size, e.g. `PDF · 1004.3 KB`. | ReactNode | — |
MessageSticker
A sticker or an emoji sent on its own.
No props of its own.
- Type
registry:ui- npm packages
@base-ui/react class-variance-authority- Files written
- components/ui/hiip-ui/message.tsx