npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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, receiveBubbleText

Light mode defaults

primary=#6852D6, neutral50=#FFFFFF, neutral900=#141414, textPrimary=#141414,
textSecondary=#727272, background1=#FFFFFF, borderDefault=#E8E8E8,
sendBubbleBackground=#6852D6, receiveBubbleBackground=#E8E8E8

Resolve 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[] | null

TypeScript 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.md

It 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