@versini/ui-bubble
v11.0.2
Published
[](https://www.npmjs.com/package/@versini/ui-bubble)  {
return (
<div className="space-y-4">
<Bubble kind="left" tail>
Hello! This is a left-aligned message.
</Bubble>
<Bubble kind="right" tail>
And this is a right-aligned reply.
</Bubble>
</div>
);
}Bubble with Footer
import { Bubble } from "@versini/ui-bubble/bubble";
function App() {
return (
<Bubble
kind="right"
tail
footer={[
{ key: "Sent", value: "12:00 PM" },
{ key: "Delivered", value: "12:01 PM" },
{ key: "Read", value: "12:02 PM" }
]}
>
Message with delivery status footer.
</Bubble>
);
}Copy to Clipboard
The Bubble component uses an action prop that accepts a React node. For copy-to-clipboard functionality, use the BubbleCopy component:
import { Bubble } from "@versini/ui-bubble/bubble";
import { BubbleCopy } from "@versini/ui-bubble/bubble-copy";
function App() {
return (
<div className="space-y-4">
{/* Simple copy - pass the text to copy as children */}
<Bubble
kind="left"
action={
<BubbleCopy>Click the copy icon to copy this message.</BubbleCopy>
}
>
Click the copy icon to copy this message.
</Bubble>
{/* With custom copy button styling */}
<Bubble
kind="right"
action={
<BubbleCopy mode="dark" focusMode="light">
Copy button with custom theme.
</BubbleCopy>
}
>
Copy button with custom theme.
</Bubble>
</div>
);
}Rich Text Copy
When you need to preserve formatting (headings, lists, bold text) when pasting into applications like Microsoft Word or Google Docs, use the richText prop:
import { Bubble } from "@versini/ui-bubble/bubble";
import { BubbleCopy } from "@versini/ui-bubble/bubble-copy";
function App() {
return (
<Bubble
kind="left"
action={
<BubbleCopy richText>
<h2>Recipe</h2>
<p>
A delicious <strong>chocolate cake</strong> with{" "}
<em>vanilla frosting</em>.
</p>
<ul>
<li>2 cups flour</li>
<li>1 cup sugar</li>
<li>3 eggs</li>
</ul>
</BubbleCopy>
}
>
<h2>Recipe</h2>
<p>
A delicious <strong>chocolate cake</strong> with{" "}
<em>vanilla frosting</em>.
</p>
<ul>
<li>2 cups flour</li>
<li>1 cup sugar</li>
<li>3 eggs</li>
</ul>
</Bubble>
);
}When richText is enabled, the clipboard will contain both HTML and plain text formats. Applications that support rich text (Word, Docs, email clients) will paste the formatted version, while plain text editors (Notepad, terminals) will receive the plain text fallback.
Custom Actions
The action prop gives you full control over what appears next to the bubble. You can use it for custom copy behavior, dropdown menus, or any other interactive elements:
import { Bubble } from "@versini/ui-bubble/bubble";
function App() {
const text = "This bubble has custom action buttons.";
return (
<Bubble
kind="left"
action={
<div className="flex gap-2">
<button
type="button"
onClick={() => navigator.clipboard.writeText(text)}
>
Copy
</button>
<button type="button" onClick={() => console.info("Share:", text)}>
Share
</button>
</div>
}
>
{text}
</Bubble>
);
}Chat Interface
import { Bubble } from "@versini/ui-bubble/bubble";
import { BubbleCopy } from "@versini/ui-bubble/bubble-copy";
function ChatExample() {
const messages = [
{ id: 1, text: "Hey, how are you?", kind: "left", time: "10:30 AM" },
{
id: 2,
text: "I'm good, thanks! How about you?",
kind: "right",
time: "10:32 AM"
},
{
id: 3,
text: "Doing great! Want to grab lunch?",
kind: "left",
time: "10:35 AM"
}
];
return (
<div className="max-w-md mx-auto space-y-3 p-4 bg-gray-50 rounded-lg">
{messages.map((message) => (
<Bubble
key={message.id}
kind={message.kind}
tail
footer={[{ key: "Time", value: message.time }]}
action={<BubbleCopy>{message.text}</BubbleCopy>}
>
{message.text}
</Bubble>
))}
</div>
);
}Advanced Footer Usage
import { Bubble } from "@versini/ui-bubble/bubble";
import { BUBBLE_FOOTER_EMPTY } from "@versini/ui-bubble/constants";
function AdvancedFooterExample() {
return (
<div className="space-y-4">
{/* Structured footer with empty row and value-only item */}
<Bubble
kind="right"
tail
footer={[
{ key: "Message ID", value: "msg-123" },
{ key: "Sent", value: "2:30 PM" },
BUBBLE_FOOTER_EMPTY, // Empty row that maintains height
{ key: "Delivered", value: "2:31 PM" },
{ key: "Read", value: "2:35 PM" },
{ value: "12/22/2025 2:36 PM EDT" } // Value only, no key displayed
]}
>
Message with detailed tracking information.
</Bubble>
{/* Raw JSX footer */}
<Bubble
kind="left"
rawFooter={
<div className="flex justify-between items-center text-xs">
<span className="text-green-600">✓ Verified</span>
<span>3:45 PM</span>
</div>
}
>
Message with custom JSX footer.
</Bubble>
</div>
);
}Custom Width Control
import { Bubble } from "@versini/ui-bubble/bubble";
function CustomWidthExample() {
return (
<div className="space-y-4">
{/* Default responsive width */}
<Bubble kind="left" tail>
This bubble has default responsive width behavior.
</Bubble>
{/* Custom width with container queries */}
<div style={{ containerType: "inline-size" }} className="w-96">
<Bubble kind="left" tail noMaxWidth className="max-w-[95cqw]">
This bubble uses container query width (95% of container width).
</Bubble>
</div>
{/* Fixed width */}
<Bubble kind="right" tail noMaxWidth className="w-64">
This bubble has a fixed width of 256px.
</Bubble>
</div>
);
}API
Bubble Props
| Prop | Type | Default | Description |
| ---------------- | -------------------------- | -------- | ------------------------------------------------------- |
| children | React.ReactNode | - | The text to render in the bubble |
| kind | "left" \| "right" | "left" | The type of Bubble (changes color and chevron location) |
| tail | boolean | false | Whether or not the Bubble should have a tail |
| action | React.ReactNode | - | Action slot content (e.g., BubbleCopy) |
| footer | BubbleFooter (see below) | - | Array of footer items for the Bubble |
| rawFooter | React.ReactNode | - | Same as "footer" but accepts raw JSX |
| noMaxWidth | boolean | false | Whether to disable default responsive max-width |
| className | string | - | CSS class(es) to add to the main component wrapper |
| contentClassName | string | - | CSS class(es) to add to the content wrapper |
BubbleCopy Props
| Prop | Type | Default | Description |
| --------- | ----------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------- |
| children | React.ReactNode | - | The content to copy (string or JSX) |
| richText | boolean | false | When true, copies as HTML + plain text. Preserves formatting when pasting into Word, Google Docs, etc |
| mode | "dark" \| "light" \| "system" \| "alt-system" | "system" | The mode of the Copy Button |
| focusMode | "dark" \| "light" \| "system" \| "alt-system" | "system" | The focus mode for the Button |
Footer Types
The footer prop accepts an array of footer items with the following types:
// Key-value pair: renders as "key: value"
{
key: string;
value: string | number;
}
// Value only: renders just the value without a key prefix
{
value: string | number;
}
// Empty row: maintains height for layout consistency
BUBBLE_FOOTER_EMPTY;Special Values
BUBBLE_FOOTER_EMPTY- Import from@versini/ui-bubble/constantsto create an empty footer row that maintains height
Migration from v10
Version 11 introduces a breaking change to the copy-to-clipboard functionality. The copyToClipboard, copyToClipboardMode, and copyToClipboardFocusMode props have been replaced with a more flexible action prop and a separate BubbleCopy component.
Before (v10)
import { Bubble } from "@versini/ui-bubble/bubble";
// Simple copy
<Bubble copyToClipboard>Content</Bubble>
// With styling
<Bubble
copyToClipboard
copyToClipboardMode="dark"
copyToClipboardFocusMode="light"
>
Content
</Bubble>
// Custom copy text
<Bubble copyToClipboard="custom text">Content</Bubble>
// Custom copy function
<Bubble copyToClipboard={(text) => customCopy(text)}>Content</Bubble>After (v11)
import { Bubble } from "@versini/ui-bubble/bubble";
import { BubbleCopy } from "@versini/ui-bubble/bubble-copy";
// Simple copy - pass text to copy as children
<Bubble action={<BubbleCopy>Content</BubbleCopy>}>
Content
</Bubble>
// With styling
<Bubble action={<BubbleCopy mode="dark" focusMode="light">Content</BubbleCopy>}>
Content
</Bubble>
// Custom copy text
<Bubble action={<BubbleCopy>custom text</BubbleCopy>}>
Content
</Bubble>
// Custom copy function - now you have full control!
<Bubble
action={
<button type="button" onClick={() => customCopy("Content")}>
Copy
</button>
}
>
Content
</Bubble>Key Changes
- New import: Add
import { BubbleCopy } from "@versini/ui-bubble/bubble-copy" - Replace props: Change
copyToClipboardtoaction={<BubbleCopy>text to copy</BubbleCopy>} - Styling props: Move
copyToClipboardMode→modeandcopyToClipboardFocusMode→focusModeonBubbleCopy - Full flexibility: The
actionprop now accepts any React node, enabling custom dropdown menus, multiple buttons, or any other UI
