[](https://www.npmjs.com/package/@versini/ui-bubble) 
!npm package minimized gzipped size>)
> A flexible and feature-rich React bubble component built with TypeScript and TailwindCSS.
The Bubble component provides chat-style message bubbles with support for footers, copy-to-clipboard functionality, and customizable positioning. Perfect for building chat interfaces, notifications, or callout sections.
- Features
- Installation
- Usage
- API
- Migration from v10
- šÆ Chat Bubbles: Left and right-aligned message bubbles with optional tails
- š Flexible Actions: Customizable action slot with built-in copy-to-clipboard component
- š Footer Support: Structured footer with key-value pairs or raw JSX
- āæ Accessible: Keyboard navigation and screen reader support
- šØ Customizable: Multiple styling options and theme support
- š² Tree-shakeable: Lightweight and optimized for bundle size
- š§ TypeScript: Fully typed with comprehensive prop definitions
``bash`
npm install @versini/ui-bubble
> Note: This component requires TailwindCSS and the @versini/ui-styles plugin for proper styling. See the installation documentation for complete setup instructions.
`tsx
import { Bubble } from "@versini/ui-bubble/bubble";
function App() {
return (
And this is a right-aligned reply.
$3
`tsx
import { Bubble } from "@versini/ui-bubble/bubble";function App() {
return (
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.
);
}
`$3
The Bubble component uses an
action prop that accepts a React node. For copy-to-clipboard functionality, use the BubbleCopy component:`tsx
import { Bubble } from "@versini/ui-bubble/bubble";
import { BubbleCopy } from "@versini/ui-bubble/bubble-copy";function App() {
return (
{/ Simple copy - pass the text to copy as children /}
kind="left"
action={
Click the copy icon to copy this message.
}
>
Click the copy icon to copy this message.
{/ With custom copy button styling /}
kind="right"
action={
Copy button with custom theme.
}
>
Copy button with custom theme.
);
}
`$3
When you need to preserve formatting (headings, lists, bold text) when pasting into applications like Microsoft Word or Google Docs, use the
richText prop:`tsx
import { Bubble } from "@versini/ui-bubble/bubble";
import { BubbleCopy } from "@versini/ui-bubble/bubble-copy";function App() {
return (
kind="left"
action={
Recipe
A delicious chocolate cake with{" "}
vanilla frosting.
- 2 cups flour
- 1 cup sugar
- 3 eggs
}
>
Recipe
A delicious chocolate cake with{" "}
vanilla frosting.
- 2 cups flour
- 1 cup sugar
- 3 eggs
);
}
`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.$3
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:`tsx
import { Bubble } from "@versini/ui-bubble/bubble";function App() {
const text = "This bubble has custom action buttons.";
return (
kind="left"
action={
type="button"
onClick={() => navigator.clipboard.writeText(text)}
>
Copy
}
>
{text}
);
}
`$3
`tsx
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 (
{messages.map((message) => (
key={message.id}
kind={message.kind}
tail
footer={[{ key: "Time", value: message.time }]}
action={{message.text} }
>
{message.text}
))}
);
}
`$3
`tsx
import { Bubble } from "@versini/ui-bubble/bubble";
import { BUBBLE_FOOTER_EMPTY } from "@versini/ui-bubble/constants";function AdvancedFooterExample() {
return (
{/ Structured footer with empty row and value-only item /}
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.
{/ Raw JSX footer /}
kind="left"
rawFooter={
ā Verified
3:45 PM
}
>
Message with custom JSX footer.
);
}
`$3
`tsx
import { Bubble } from "@versini/ui-bubble/bubble";function CustomWidthExample() {
return (
{/ Default responsive width /}
This bubble has default responsive width behavior.
{/ Custom width with container queries /}
This bubble uses container query width (95% of container width).
{/ Fixed width /}
This bubble has a fixed width of 256px.
);
}
`API
$3
| 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 |$3
| 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 |$3
The
footer prop accepts an array of footer items with the following types:`typescript
// 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;
`$3
-
BUBBLE_FOOTER_EMPTY - Import from @versini/ui-bubble/constants to create an empty footer row that maintains heightMigration 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.$3
`tsx
import { Bubble } from "@versini/ui-bubble/bubble";// Simple copy
Content
// With styling
copyToClipboard
copyToClipboardMode="dark"
copyToClipboardFocusMode="light"
>
Content
// Custom copy text
Content
// Custom copy function
customCopy(text)}>Content
`$3
`tsx
import { Bubble } from "@versini/ui-bubble/bubble";
import { BubbleCopy } from "@versini/ui-bubble/bubble-copy";// Simple copy - pass text to copy as children
Content}>
Content
// With styling
Content}>
Content
// Custom copy text
custom text}>
Content
// Custom copy function - now you have full control!
action={
}
>
Content
`$3
1. New import: Add
import { BubbleCopy } from "@versini/ui-bubble/bubble-copy"
2. Replace props: Change copyToClipboard to action={
3. Styling props: Move copyToClipboardMode ā mode and copyToClipboardFocusMode ā focusMode on BubbleCopy
4. Full flexibility: The action` prop now accepts any React node, enabling custom dropdown menus, multiple buttons, or any other UI