Componentmedia
ChatBubble
Single message bubble with tail, status ticks, and hover press feedback.
Preview
Live Preview
ChatBubble
Hey there! How's it going?
Hey there! How's it going?
I'm doing well, thanks!
I'm doing well, thanks!
Installation
Tokens guide →Add to your project via CLI (zero dependencies, code you own):
npx bigbullui add chat-bubbleOr install the full npm package:
npm install bigbulluiUsage
import { ChatBubble } from "@/components/ui/chat-bubble";
<ChatBubble direction="outgoing">Message body<ChatStatus sent={true} delivered={true} read={false} /></ChatBubble>Source code
chat-bubble.tsxZero dependencies (only React + utils). Copy and paste directly into your project:
"use client";
import * as React from "react";
import { cn } from "./lib/utils";
export interface ChatStatus {
sent?: boolean;
delivered?: boolean;
read?: boolean;
}
export interface ChatBubbleProps {
direction: "incoming" | "outgoing";
children: React.ReactNode;
status?: ChatStatus;
}
const ChatBubble: React.FC<ChatBubbleProps> = ({ direction, children, status }) => {
const [isPressed, setIsPressed] = React.useState(false);
return (
<div
className={cn(
"flex flex-col gap-1 relative min-w-[40px]",
direction === "outgoing"
? "items-end"
: "items-start"
)}
>
{children}
{status && (
<div
className={cn(
"absolute -bottom-1 -left-1 flex gap-0.5 text-[10px] mono uppercase text-muted-foreground",
direction === "outgoing" ? "-right-1" : "-left-1"
)}
>
{[status.sent && "S", status.delivered && "D", status.read && "R"]
.filter(Boolean)
.join(" ")}
</div>
)}
{/* Tail triangle for incoming, rotated square for outgoing */}
{direction === "incoming" ? (
<div
className={cn(
"absolute -inset-0.5 pointer-events-none",
"mx-auto w-0 border-2 border-transparent border-border/50"
)}
>
<div
className={cn(
"mx-auto w-0 border-2 border-border/60",
"border-t-transparent border-b-2"
)}
/>
</div>
) : (
<div
className={cn(
"absolute -inset-0.5 pointer-events-none rotate-6",
"w-0 h-0 border-2 border-transparent border-border/50"
)}
>
<div
className={cn(
"w-0 h-0 border-2 border-border/60",
"border-t-transparent border-b-2 rotate-6"
)}
/>
</div>
)}
{/* Press feedback */}
<div
onMouseDown={() => setIsPressed(true)}
onMouseUp={() => setIsPressed(false)}
onMouseLeave={() => setIsPressed(false)}
className={cn(
"transition-transform duration-150",
isPressed ? "scale-[0.96]" : ""
)}
>
{children}
</div>
</div>
);
};
export { ChatBubble };Props
| Prop | Type | Description |
|---|---|---|
| direction | "incoming" | "outgoing" | Message direction for tail positioning. |
| children | React.ReactNode | Message content. |
| status | { sent?: boolean; delivered?: boolean; read?: boolean } | Status ticks (sent/delivered/read). |