Skip to main content

iOS Message Bubbles

Beautiful iOS-style message bubbles with authentic iMessage appearance and tail effects.

CardReactTailwind CSS
CSSshadcnCustom 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.

Similar components

Currency Converter

Agent Usage Metrics

Team Cards with Gradient Hover

Max

Dashboard Chart

Resource details

PublishedMarch 3, 2026
CategoryCard
ReactTailwind CSS