Chat Bubble
Message bubble for chat interfaces with sent/received variants.
Installation#
npx @aniui/cli add chat-bubbleHey, how are you?
2:30 PMI'm doing great, thanks!
2:31 PMWeb preview — components render natively on iOS & Android
import { ChatBubble } from "@/components/ui/chat-bubble";
export function MyScreen() {
return (
<View className="gap-3 p-4">
<ChatBubble variant="received" timestamp="2:30 PM">
Hey, how are you?
</ChatBubble>
<ChatBubble variant="sent" timestamp="2:31 PM" status="read">
I'm doing great, thanks!
</ChatBubble>
<ChatBubble variant="sent" timestamp="2:31 PM" status="delivered">
How about you?
</ChatBubble>
</View>
);
}Usage#
app/index.tsx
import { ChatBubble } from "@/components/ui/chat-bubble";
export function MyScreen() {
return (
<View className="gap-3 p-4">
<ChatBubble variant="received" timestamp="2:30 PM">
Hey, how are you?
</ChatBubble>
<ChatBubble variant="sent" timestamp="2:31 PM" status="read">
I'm doing great, thanks!
</ChatBubble>
<ChatBubble variant="sent" timestamp="2:31 PM" status="delivered">
How about you?
</ChatBubble>
</View>
);
}Props#
PropTypeDefault
variant"sent" | "received""received"childrenReact.ReactNode-timestampstring-status"sent" | "delivered" | "read"-classNamestring-Also accepts all View props.
Accessibility#
- Message bubble with sent/received styling for visual distinction.
- Message content is readable by screen readers with sender context.
Source#
components/ui/chat-bubble.tsx
import React from "react";
import { View, Text } from "react-native";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const bubbleVariants = cva("max-w-[80%] rounded-2xl px-4 py-2.5", {
variants: {
variant: {
sent: "bg-primary self-end rounded-br-sm",
received: "bg-secondary self-start rounded-bl-sm",
},
},
defaultVariants: { variant: "received" },
});
const textVariants = cva("text-base", {
variants: {
variant: {
sent: "text-primary-foreground",
received: "text-secondary-foreground",
},
},
defaultVariants: { variant: "received" },
});
export interface ChatBubbleProps
extends React.ComponentPropsWithoutRef<typeof View>,
VariantProps<typeof bubbleVariants> {
className?: string;
children: React.ReactNode;
timestamp?: string;
status?: "sent" | "delivered" | "read";
}
const statusIcons: Record<string, string> = {
sent: "✓",
delivered: "✓✓",
read: "✓✓",
};
export function ChatBubble({
variant,
className,
children,
timestamp,
status,
...props
}: ChatBubbleProps) {
const isSent = variant === "sent";
return (
<View className={cn(bubbleVariants({ variant }), className)} {...props}>
<Text className={textVariants({ variant })}>{children}</Text>
{(timestamp || status) && (
<View className={cn("flex-row items-center gap-1 mt-1", isSent ? "self-end" : "self-start")}>
{timestamp && (
<Text className={cn("text-[10px]", isSent ? "text-primary-foreground/60" : "text-muted-foreground")}>
{timestamp}
</Text>
)}
{status && isSent && (
<Text className={cn("text-[10px]", status === "read" ? "text-blue-300" : "text-primary-foreground/60")}>
{statusIcons[status]}
</Text>
)}
</View>
)}
</View>
);
}