Chat Bubble
Message bubble with markdown, avatar tooltips, delivery status, streaming cursor, and BubbleReactions attached to the bubble edge.
Gallery
Assistant, user with reactions, and system line.
System maintenance in 10 minutes.
default-example.tsx
1"use client";23import { useState } from "react";4import { ChatBubble } from "@/registry/ui/chat-bubble";5import type { Reaction } from "@/registry/ui/chat-types";67const ASSISTANT_MARKDOWN_DEMO = `Hi! **Markdown** works here, including \`inline code\` and lists:89- First item with a bullet10- Second item1112Numbered:13141. Step one152. Step two1617Fenced block:1819\`\`\`ts20const greeting = "hello";21console.log(greeting);22\`\`\`2324And a one-line fence without a language:2526\`\`\`27npm run build28\`\`\``;2930const INITIAL_REACTIONS: Reaction[] = [31 { emoji: "๐", count: 1, reacted: true },32 { emoji: "๐", count: 2, reacted: false },33];3435const REACTION_OPTIONS = [36 "๐",37 "โค๏ธ",38 "๐",39 "๐",40 "๐ฅ",41 "๐",42 "๐ค",43 "๐",44 "๐ฏ",45 "๐",46 "โจ",47 "๐",48];4950export function ChatBubbleDefaultExample() {51 const [reactions, setReactions] = useState(INITIAL_REACTIONS);5253 function addReaction(emoji: string) {54 setReactions((current) => {55 const existing = current.find((reaction) => reaction.emoji === emoji);5657 if (!existing) {58 return [...current, { emoji, count: 1, reacted: true }];59 }6061 return current.map((reaction) =>62 reaction.emoji === emoji63 ? {64 ...reaction,65 count: reaction.count + (reaction.reacted ? 0 : 1),66 reacted: true,67 }68 : reaction,69 );70 });71 }7273 function removeReaction(emoji: string) {74 setReactions((current) =>75 current.flatMap((reaction) => {76 if (reaction.emoji !== emoji) return [reaction];77 if (reaction.count === 1) return [];78 return [{ ...reaction, count: reaction.count - 1, reacted: false }];79 }),80 );81 }8283 return (84 <div className="mx-auto flex w-full max-w-lg flex-col gap-8 p-4">85 <ChatBubble86 content="Can you show me the markdown support and attach the image you used?"87 variant="user"88 timestamp={new Date()}89 status="read"90 user={{ id: "2", name: "You" }}91 />92 <ChatBubble93 content={ASSISTANT_MARKDOWN_DEMO}94 variant="assistant"95 timestamp={new Date()}96 user={{ id: "1", name: "Assistant" }}97 attachments={[98 {99 id: "abstract-gradient",100 type: "image",101 name: "abstract-gradient.jpg",102 url: "https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?q=80&w=1200&auto=format&fit=crop",103 size: 824320,104 },105 ]}106 reactions={reactions}107 reactionOptions={REACTION_OPTIONS}108 onReact={addReaction}109 onRemoveReaction={removeReaction}110 />111 <ChatBubble112 content="System maintenance in 10 minutes."113 variant="system"114 />115 </div>116 );117}
Minimal
Minimal chat bubble with no avatar or reactions.
minimal-example.tsx
1"use client";23import { ChatBubble } from "@/registry/ui/chat-bubble";45export function ChatBubbleMinimalExample() {6 return (7 <div className="mx-auto flex w-full max-w-lg flex-col gap-8 p-4">8 <ChatBubble9 content="Sounds good - here's my reply with a read receipt."10 variant="user"11 timestamp={new Date()}12 status="read"13 user={{ id: "2", name: "You" }}14 reactions={[15 { emoji: "๐", count: 1, reacted: true },16 { emoji: "๐", count: 2, reacted: false },17 ]}18 />19 </div>20 );21}
Installation & source
Install via the shadcn CLI or copy the registry files manually.
bash
npx shadcn@latest add @tt-ui/chat-bubble
Props
| Name | Type | Default | Description |
|---|---|---|---|
| content | string | - | Markdown body rendered with GFM. |
| variant | "user" | "assistant" | "system" | "user" | Layout and palette; system renders a separator marker. |
| status | MessageStatus | undefined | Optional read receipts for user messages. |