iOS Message Bubbles
Beautiful iOS-style message bubbles with authentic iMessage appearance and tail effects.
Category
CardReact
CSS
shadcnCustom CSS
Manual
Create a file and paste the following code into it.
message-bubble.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
"use client";
import "./message-bubble.css";
import { cn } from "@/lib/cn";
export interface MessageBubbleProps {
message: string;
variant?: "sent" | "received";
grouped?: "first" | "middle" | "last" | "none";
className?: string;
children?: React.ReactNode;
}
export function MessageBubble({
message,
variant = "received",
grouped = "none",
className,
children,
}: MessageBubbleProps) {
const groupedClasses =
grouped === "first"
? "imessage-grouped-first mb-1.5"
: grouped === "last"
? "imessage-grouped-last"
: grouped === "middle"
? "imessage-grouped-middle mb-1.5"
: "";
return (
<div
className={cn(
"imessage-bubble",
variant === "sent" ? "imessage-from-me" : "imessage-from-them",
groupedClasses,
className,
)}
>
{children || <p className="whitespace-pre-wrap">{message}</p>}
</div>
);
}
export interface ChatMessageProps {
timestamp?: string;
messages: string[];
variant?: "sent" | "received";
className?: string;
showTimestamp?: boolean;
}
export function ChatMessage({
timestamp,
messages,
variant = "received",
className,
showTimestamp = true,
}: ChatMessageProps) {
const hasMultipleMessages = messages.length > 1;
const getGroupedType = (index: number, total: number) => {
if (total === 1) return "none";
if (index === 0) return "first";
if (index === total - 1) return "last";
return "middle";
};
return (
<div className={cn("flex w-full flex-col", className)}>
<div className="flex flex-col">
{messages.map((message, index) => (
<MessageBubble
key={index}
message={message}
variant={variant}
grouped={
hasMultipleMessages
? getGroupedType(index, messages.length)
: "none"
}
/>
))}
</div>
{showTimestamp && timestamp && (
<span
className={cn(
"mt-1 px-2 text-xs text-muted-foreground",
variant === "sent" && "text-right",
)}
>
{timestamp}
</span>
)}
</div>
);
}
Update the import paths to match your project setup.