All components

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.
Y

Can you show me the markdown support and attach the image you used?

12:34 AM
A

Hi! Markdown works here, including inline code and lists:

  • First item with a bullet
  • Second item

Numbered:

  1. Step one
  2. Step two

Fenced block:

ts
const greeting = "hello";
console.log(greeting);

And a one-line fence without a language:

text
npm run build
abstract-gradient.jpgJPG ยท 805 KB
12:34 AM
System maintenance in 10 minutes.
1"use client";
2
3import { useState } from "react";
4import { ChatBubble } from "@/registry/ui/chat-bubble";
5import type { Reaction } from "@/registry/ui/chat-types";
6
7const ASSISTANT_MARKDOWN_DEMO = `Hi! **Markdown** works here, including \`inline code\` and lists:
8
9- First item with a bullet
10- Second item
11
12Numbered:
13
141. Step one
152. Step two
16
17Fenced block:
18
19\`\`\`ts
20const greeting = "hello";
21console.log(greeting);
22\`\`\`
23
24And a one-line fence without a language:
25
26\`\`\`
27npm run build
28\`\`\``;
29
30const INITIAL_REACTIONS: Reaction[] = [
31 { emoji: "๐Ÿ‘", count: 1, reacted: true },
32 { emoji: "๐ŸŽ‰", count: 2, reacted: false },
33];
34
35const REACTION_OPTIONS = [
36 "๐Ÿ‘",
37 "โค๏ธ",
38 "๐Ÿ˜‚",
39 "๐ŸŽ‰",
40 "๐Ÿ”ฅ",
41 "๐Ÿ‘",
42 "๐Ÿค”",
43 "๐Ÿ‘€",
44 "๐Ÿ’ฏ",
45 "๐Ÿš€",
46 "โœจ",
47 "๐Ÿ™Œ",
48];
49
50export function ChatBubbleDefaultExample() {
51 const [reactions, setReactions] = useState(INITIAL_REACTIONS);
52
53 function addReaction(emoji: string) {
54 setReactions((current) => {
55 const existing = current.find((reaction) => reaction.emoji === emoji);
56
57 if (!existing) {
58 return [...current, { emoji, count: 1, reacted: true }];
59 }
60
61 return current.map((reaction) =>
62 reaction.emoji === emoji
63 ? {
64 ...reaction,
65 count: reaction.count + (reaction.reacted ? 0 : 1),
66 reacted: true,
67 }
68 : reaction,
69 );
70 });
71 }
72
73 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 }
82
83 return (
84 <div className="mx-auto flex w-full max-w-lg flex-col gap-8 p-4">
85 <ChatBubble
86 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 <ChatBubble
93 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 <ChatBubble
112 content="System maintenance in 10 minutes."
113 variant="system"
114 />
115 </div>
116 );
117}
Minimal
Minimal chat bubble with no avatar or reactions.
Y

Sounds good - here's my reply with a read receipt.

12:34 AM
1"use client";
2
3import { ChatBubble } from "@/registry/ui/chat-bubble";
4
5export function ChatBubbleMinimalExample() {
6 return (
7 <div className="mx-auto flex w-full max-w-lg flex-col gap-8 p-4">
8 <ChatBubble
9 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

NameTypeDefaultDescription
contentstring-Markdown body rendered with GFM.
variant"user" | "assistant" | "system""user"Layout and palette; system renders a separator marker.
statusMessageStatusundefinedOptional read receipts for user messages.