@mathews_cometchat/bubble-renderer
v1.0.0-alpha1
Published
A lightweight React library for rendering CometChat Bubble Message JSON into interactive UI components. Zero dependencies beyond React. ~6.4 kB gzipped.
Readme
@mathews_cometchat/bubble-renderer
A lightweight React library for rendering CometChat Bubble Message JSON into interactive UI components. Zero dependencies beyond React. ~6.4 kB gzipped.
Feed it a BubbleMessage JSON → it renders a fully styled, interactive bubble with 20 element types and 9 action types.
Installation
{ "dependencies": { "@mathews_cometchat/bubble-renderer": "file:../bubble-renderer" } }Peer dependency: react >= 18.0.0
Quick Start
import { BubbleRenderer } from '@mathews_cometchat/bubble-renderer';
import type { BubbleMessage, BubbleAction, BubbleElement } from '@mathews_cometchat/bubble-renderer';
const bubble: BubbleMessage = {
version: '1.0',
fallbackText: 'Order confirmed!',
body: [
{ id: '1', type: 'text', content: 'Order Confirmed ✅', variant: 'heading2', color: '#09C26F' },
{ id: '2', type: 'divider' },
{ id: '3', type: 'text', content: 'Total: $49.99', variant: 'body' },
{ id: '4', type: 'button', label: 'Track Order',
action: { type: 'openUrl', url: 'https://example.com/track' },
backgroundColor: '#6852D6', textColor: '#FFFFFF', borderRadius: 6 },
],
settings: { background: '#FFFFFF', borderRadius: 12, padding: 16 },
};
function handleAction(action: BubbleAction, element: BubbleElement) {
if (action.type === 'openUrl') window.open(action.url, '_blank');
if (action.type === 'sendMessage') console.log('Send:', action.text);
}
<BubbleRenderer bubble={bubble} onAction={handleAction} />Components
<BubbleRenderer>
Renders a complete BubbleMessage. Main entry point.
| Prop | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| bubble | BubbleMessage | yes | — | The bubble JSON to render |
| onAction | (action: BubbleAction, element: BubbleElement) => void | no | — | Callback for button/link/iconButton clicks. If omitted, interactive elements are static. |
| className | string | no | — | CSS class on outer wrapper |
| style | React.CSSProperties | no | — | Inline styles merged onto bubble wrapper (overrides settings) |
<ElementRenderer>
Renders a single BubbleElement. Use for rendering individual elements outside a bubble.
| Prop | Type | Required |
|------|------|----------|
| element | BubbleElement | yes |
| onAction | (action: BubbleAction, element: BubbleElement) => void | no |
Root Schema
BubbleMessage
{
version: '1.0'; // Always '1.0'
body: BubbleElement[]; // Array of elements (required, min 1)
settings?: BubbleSettings; // Bubble container styling
fallbackText: string; // Plain text fallback (required)
notificationText?: string; // Push notification text
}BubbleSettings
{
background?: string; // Hex color. Default: '#E8E8E8'
borderRadius?: number; // px. Default: 16
borderColor?: string; // Hex color
borderWidth?: number; // px
padding?: Padding; // number (uniform px) or { top?, right?, bottom?, left? }
}Padding type
Padding = number | { top?: number; right?: number; bottom?: number; left?: number }
All padding/spacing values are in pixels.
Complete Element Reference
Every element requires id: string and type: string. All other properties are optional unless marked (required).
Colors are hex strings (e.g. '#6852D6') or theme tokens (e.g. '{{theme.color.primary}}').
text
Renders a text block with typography variants.
{
id: string;
type: 'text';
content: string; // (required) Text to display. Supports {{placeholder}} tokens.
variant?: 'title' | 'heading1' | 'heading2' | 'heading3' | 'heading4' | 'body' | 'caption1' | 'caption2';
// Default: 'body'
// title=32px/700, heading1=24px/700, heading2=20px/600,
// heading3=18px/600, heading4=16px/600, body=14px/400,
// caption1=12px/400, caption2=10px/400
color?: string; // Default: '#2D3436'
align?: 'left' | 'center' | 'right' | 'justify'; // Default: 'left'
fontWeight?: 'regular' | 'medium' | 'bold'; // Default: 'regular' (400/500/700)
maxLines?: number; // Clamp text to N lines with ellipsis
padding?: Padding;
}image
Renders an image or a placeholder if URL is empty.
{
id: string;
type: 'image';
url: string; // (required) Image URL. Empty string shows placeholder.
altText?: string; // Default: ''
fit?: 'cover' | 'contain' | 'fill'; // Default: 'cover'
width?: number | string; // px or percentage string (e.g. '50%'). Default: '100%'
height?: number | string; // px or percentage string. Default: 'auto' (180px for placeholder)
borderRadius?: number; // px. Default: 0 (6 for placeholder)
padding?: Padding;
}icon
Renders a tintable icon using CSS mask-image.
{
id: string;
type: 'icon';
name: string; // (required) Icon name from library OR a URL
size?: number; // px. Default: 20
color?: string; // Tint color. Default: '#666666'
backgroundColor?: string; // Background behind icon
borderRadius?: number; // Default: 0
padding?: number; // px around icon. Default: 4
}avatar
Circular avatar with image or fallback initials.
{
id: string;
type: 'avatar';
imageUrl?: string; // If provided, renders image
fallbackInitials?: string; // Shown when no imageUrl. Default: '?'
size?: number; // px. Default: 36
borderRadius?: number; // Default: size/2 (circle)
backgroundColor?: string; // Default: '#6C5CE7'
fontSize?: number; // Initials font size. Default: size * 0.4
fontWeight?: 'regular' | 'medium' | 'bold'; // Default: 'regular'
}badge
Small label or counter.
{
id: string;
type: 'badge';
text: string; // (required)
color?: string; // Text color. Default: '#FFFFFF'
backgroundColor?: string; // Default: '#6C5CE7'
size?: number; // Height in px. Default: 24
borderRadius?: number; // Default: 12
borderColor?: string;
borderWidth?: number;
padding?: Padding;
fontSize?: number; // Default: 12
}divider
Horizontal rule.
{
id: string;
type: 'divider';
color?: string; // Default: '#e0e0e0'
thickness?: number; // px. Default: 1
margin?: number; // Vertical margin in px. Default: 8
}spacer
Empty vertical space.
{
id: string;
type: 'spacer';
height: number; // (required) px
}chip
Tag/chip with optional icon.
{
id: string;
type: 'chip';
text: string; // (required)
color?: string; // Text color. Default: '#333333'
icon?: string; // Icon name or URL
backgroundColor?: string; // Default: '#e0e0e0'
borderColor?: string;
borderWidth?: number;
borderRadius?: number; // Default: 16
padding?: Padding; // Default: '4px 10px'
fontSize?: number; // Default: 12
}progressBar
Horizontal progress indicator.
{
id: string;
type: 'progressBar';
value: number; // (required) 0–100
color?: string; // Fill color. Default: '#6C5CE7'
trackColor?: string; // Background track. Default: '#e0e0e0'
height?: number; // px. Default: 6
label?: string; // Text above bar
borderRadius?: number; // Default: 3
labelFontSize?: number; // Default: 12
labelColor?: string; // Default: '#666666'
}codeBlock
Preformatted code block.
{
id: string;
type: 'codeBlock';
content: string; // (required) Code text
language?: string; // Shown as label (e.g. 'javascript')
backgroundColor?: string; // Default: '#1e1e1e'
textColor?: string; // Default: '#d4d4d4'
padding?: Padding; // Default: 12
borderRadius?: number; // Default: 4
fontSize?: number; // Default: 12
languageLabelFontSize?: number; // Default: 10
languageLabelColor?: string; // Default: '#888888'
}markdown
Renders markdown content as plain text (no parsing).
{
id: string;
type: 'markdown';
content: string; // (required)
baseFontSize?: number; // Default: 14
color?: string; // Default: '#333333'
lineHeight?: number; // Default: 1.5
linkColor?: string; // Default: '#6C5CE7'
}row
Horizontal flex container. Children are rendered left-to-right.
{
id: string;
type: 'row';
items: BubbleElement[]; // (required) Child elements
gap?: number; // px between children. Default: 0
align?: 'start' | 'center' | 'end' | 'spaceBetween' | 'spaceAround'; // Default: 'start'
wrap?: boolean; // Wrap to next line. Default: false
scrollable?: boolean; // Horizontal scroll mode. Default: false
peek?: number; // px of next item visible when scrollable. Default: 0
snap?: 'item' | 'free'; // Scroll snap behavior. Default: 'item'
padding?: Padding;
backgroundColor?: string;
borderRadius?: number;
borderColor?: string;
borderWidth?: number;
}column
Vertical flex container. Children are rendered top-to-bottom.
{
id: string;
type: 'column';
items: BubbleElement[]; // (required) Child elements
gap?: number; // px between children. Default: 0
align?: 'start' | 'center' | 'end' | 'stretch'; // Default: 'start'
padding?: Padding;
backgroundColor?: string;
borderRadius?: number;
borderColor?: string;
borderWidth?: number;
}grid
CSS grid layout.
{
id: string;
type: 'grid';
items: BubbleElement[]; // (required) Child elements
columns?: 2 | 3 | 4; // Default: 2
gap?: number; // px. Default: 0
padding?: Padding;
backgroundColor?: string;
borderRadius?: number;
borderColor?: string;
borderWidth?: number;
}accordion
Collapsible section with header and body.
{
id: string;
type: 'accordion';
header: string; // (required) Header text
headerIcon?: string; // Icon name or URL
body: BubbleElement[]; // (required) Child elements shown when expanded
expandedByDefault?: boolean; // Default: false
border?: boolean; // Show border. Default: false
padding?: Padding; // Default: 8
fontSize?: number; // Header font size. Default: 14
fontWeight?: 'regular' | 'medium' | 'bold'; // Default: 'regular'
borderRadius?: number; // Default: 4
}tabs
Tabbed content panels. Each tab has a label and content array.
{
id: string;
type: 'tabs';
tabs: Array<{ // (required, min 1)
label: string; // Tab label text
content: BubbleElement[]; // Elements shown when tab is active
}>;
defaultActiveTab?: number; // 0-based index. Default: 0
tabAlign?: 'start' | 'center' | 'stretch'; // Default: 'start'
tabPadding?: Padding; // Default: '8px 16px'
contentPadding?: Padding; // Default: 8
fontSize?: number; // Tab label font size. Default: 13
}button
Styled button with action.
{
id: string;
type: 'button';
label: string; // (required) Button text
action: BubbleAction; // (required) Action on click
backgroundColor?: string; // Default: 'transparent'
textColor?: string; // Default: '#FFFFFF'
borderColor?: string;
borderWidth?: number;
borderRadius?: number; // Default: 6
icon?: string; // Icon name or URL
iconPosition?: 'left' | 'right'; // Default: 'left'
size?: number; // Button height in px. Default: 40
fullWidth?: boolean; // Stretch to 100% width. Default: false
padding?: Padding; // Default: '0 16px'
fontSize?: number; // Default: max(11, size * 0.325)
}iconButton
Circular icon-only button.
{
id: string;
type: 'iconButton';
icon: string; // (required) Icon name or URL
action: BubbleAction; // (required) Action on click
size?: number; // px (width & height). Default: 32
color?: string; // Icon tint. Default: '#333333'
backgroundColor?: string; // Default: '#f0f0f0'
borderRadius?: number; // Default: size/2 (circle)
}link
Inline text link with action.
{
id: string;
type: 'link';
text: string; // (required) Link text. Supports {{placeholder}} tokens.
action: BubbleAction; // (required) Action on click
color?: string; // Default: '#6C5CE7'
underline?: boolean; // Default: true
fontSize?: number; // Default: 14
}table
Data table with headers and rows.
{
id: string;
type: 'table';
columns: string[]; // (required) Header labels
rows: string[][]; // (required) Row data. Each row is array of cell strings.
headerBackgroundColor?: string; // Default: '#f5f5f5'
border?: boolean; // Show cell borders. Default: false
stripedRows?: boolean; // Alternate row colors. Default: false
cellPadding?: number; // px. Default: 6
fontSize?: number; // Default: 12
stripedRowColor?: string; // Default: '#fafafa'
borderColor?: string; // Default: '#e0e0e0'
}Actions Reference
Actions are attached to button, iconButton, and link elements via the action property. The onAction callback receives the action object when clicked.
// Open a URL
{ type: 'openUrl', url: string, openIn?: 'browser' | 'webview' }
// Navigate to 1:1 chat
{ type: 'chatWithUser', uid: string }
// Navigate to group chat
{ type: 'chatWithGroup', guid: string }
// Send a message
{ type: 'sendMessage', text: string, receiverUid?: string, receiverGuid?: string }
// Copy text to clipboard
{ type: 'copyToClipboard', value: string }
// Download a file
{ type: 'downloadFile', url: string, filename?: string }
// Start audio/video call
{ type: 'initiateCall', uid?: string, guid?: string, callType: 'audio' | 'video' }
// Make HTTP request
{ type: 'apiCall', url: string, method?: 'GET' | 'POST' | 'PUT' | 'DELETE', headers?: Record<string, string>, body?: Record<string, unknown> }
// Custom callback (no payload)
{ type: 'customCallback' }Icon Library
50 built-in icons served from CDN. Use the name string in any icon field. You can also pass any URL directly.
All available icon names
Navigation: arrow_back, arrow_forward, close, check, check_circle, add, remove, edit, delete, search, refresh
Communication: chat, mail, phone, call, videocam, send, notifications
People: person, group, account_circle, account_box, favorite, star, thumb_up, thumb_down, share
Content: link, attach_file, image, file_copy, download, upload
Status: info, warning, error, help, schedule, calendar_today, location_on
Commerce: shopping_cart, credit_card, payments, account_balance, account_balance_wallet, add_card, add_shopping_cart
Misc: add_home, add_task
Usage
// In element JSON — use name directly
{ type: 'icon', name: 'check_circle', size: 24, color: '#09C26F' }
// Or use any URL
{ type: 'icon', name: 'https://my-cdn.com/custom-icon.png', size: 24 }
// Programmatic resolution
import { resolveIconUrl } from '@mathews_cometchat/bubble-renderer';
resolveIconUrl('check_circle'); // → 'https://assets.cc-cluster-2.io/bubble-builder/check_circle.png'Theming
Colors in the JSON can be raw hex ('#6852D6') or theme tokens ('{{theme.color.primary}}').
Switch light/dark
import { setActiveTheme } from '@mathews_cometchat/bubble-renderer';
setActiveTheme(true); // dark
setActiveTheme(false); // light (default)Override tokens
import { setCustomThemeTokens } from '@mathews_cometchat/bubble-renderer';
setCustomThemeTokens({ primary: '#FF6B6B', textPrimary: '#1A1A2E' });All theme token names
primary
extendedPrimary50, extendedPrimary100, extendedPrimary200, extendedPrimary300, extendedPrimary400
extendedPrimary500, extendedPrimary600, extendedPrimary700, extendedPrimary800, extendedPrimary900
neutral50, neutral100, neutral200, neutral300, neutral400
neutral500, neutral600, neutral700, neutral800, neutral900
info, warning, success, error
staticBlack, staticWhite
background1, background2, background3, background4
textPrimary, textSecondary, textTertiary, textDisabled, textWhite, textHighlight
borderLight, borderDefault, borderDark, borderHighlight
iconPrimary, iconSecondary, iconHighlight
primaryButtonBackground, primaryButtonText
sendBubbleBackground, sendBubbleText
receiveBubbleBackground, receiveBubbleTextLight mode defaults
primary=#6852D6, neutral50=#FFFFFF, neutral900=#141414, textPrimary=#141414,
textSecondary=#727272, background1=#FFFFFF, borderDefault=#E8E8E8,
sendBubbleBackground=#6852D6, receiveBubbleBackground=#E8E8E8Resolve manually
import { resolveColor } from '@mathews_cometchat/bubble-renderer';
resolveColor('{{theme.color.primary}}'); // → '#6852D6'
resolveColor('#FF0000'); // → '#FF0000'
resolveColor(undefined, '#000'); // → '#000' (fallback)Utilities
import { assignIds, generateId, isContainerElement, getContainerChildren } from '@mathews_cometchat/bubble-renderer';
// Assign IDs to elements missing them (recursive)
const withIds = assignIds(elements);
// Generate a single prefixed ID
generateId('button'); // → 'btn_a1b2c3d4-...'
generateId('text'); // → 'txt_e5f6g7h8-...'
// Check if element is a container (row/column/grid/accordion)
isContainerElement(el); // → boolean
// Get children of a container element
getContainerChildren(el); // → BubbleElement[] | nullTypeScript Exports
import type {
BubbleMessage, BubbleElement, BubbleAction, BubbleSettings,
ElementType, ActionType, Padding, SizeOrPercentage, ColorValue,
// 20 element types
TextElement, ImageElement, IconElement, AvatarElement, BadgeElement,
DividerElement, SpacerElement, ChipElement, ProgressBarElement,
CodeBlockElement, MarkdownElement, RowElement, ColumnElement,
GridElement, AccordionElement, TabsElement, ButtonElement,
IconButtonElement, LinkElement, TableElement,
// 9 action types
OpenUrlAction, ChatWithUserAction, ChatWithGroupAction, SendMessageAction,
CopyToClipboardAction, DownloadFileAction, InitiateCallAction, ApiCallAction,
CustomCallbackAction,
// Component props
BubbleRendererProps, ElementRendererProps, ActionHandler,
IconEntry,
} from '@mathews_cometchat/bubble-renderer';AI / Kiro Skill
This package ships with a SKILL.md file that gives AI assistants (like Kiro) full context about the library — every element type, property, default, action, and icon name. To enable it in your project:
# Copy into your .kiro/skills directory
cp node_modules/@mathews_cometchat/bubble-renderer/SKILL.md .kiro/skills/bubble-renderer/SKILL.mdIt also ships bubble-schema.json — a complete JSON Schema (draft-07) for validating BubbleMessage JSON.
Build
npm install
npm run build # → dist/bubble-renderer.es.js + dist/bubble-renderer.umd.js + dist/index.d.ts