chatgram-widget
v1.0.5
Published
Production-grade embeddable contact form widget for Chatgram — React, Vue, vanilla JS
Maintainers
Readme
chatgram-widget
Production-grade embeddable contact form widget for Chatgram.
Works with React, Vue, vanilla JS — or any frontend via <script> tag.
Features
- Zero dependencies — pure vanilla JS, no React/Vue/jQuery required
- Shadow DOM isolation — widget CSS never leaks into your site (and vice versa)
- Full accessibility — ARIA roles, focus trapping, keyboard navigation,
prefers-reduced-motion, high contrast mode - Anti-spam — honeypot field, timing-based bot detection, client-side rate limiting
- Resilient API client — timeout handling, exponential backoff retry, structured error codes
- Runtime theming — switch colors on the fly without re-init
- Event system — subscribe to
open,close,submit,success,error,state-change - ESM + CJS + UMD — tree-shakeable, CDN-ready, works everywhere
- TypeScript-first — complete type definitions and IntelliSense
- SSR-safe — no
window/documentaccess at import time
Install
npm install chatgram-widgetCDN
<script src="https://cdn.jsdelivr.net/npm/chatgram-widget"></script>or
<script src="https://unpkg.com/chatgram-widget"></script>Quick Start
Vanilla JS (CDN)
<script src="https://cdn.jsdelivr.net/npm/chatgram-widget"></script>
<script>
window.addEventListener('load', function () {
ChatgramWidget.init({
showTriggerButton: false,
triggerPosition: 'bottom-right',
theme: {
primaryColor: '#EE6055',
secondaryColor: '#D94F44',
},
});
document.getElementById('openBtn').addEventListener('click', function () {
ChatgramWidget.open();
});
document.getElementById('closeBtn').addEventListener('click', function () {
ChatgramWidget.close();
});
document.getElementById('destroyBtn').addEventListener('click', function () {
ChatgramWidget.destroy();
});
});
</script>ES Module
import { init, open, close, destroy } from 'chatgram-widget';
init({
showTriggerButton: false,
theme: {
primaryColor: '#EE6055',
secondaryColor: '#D94F44',
},
});
document.querySelector('#open-btn').addEventListener('click', open);
document.querySelector('#close-btn').addEventListener('click', close);
document.querySelector('#destroy-btn').addEventListener('click', destroy);React
import { useEffect } from 'react';
import { init, open, destroy } from 'chatgram-widget';
function App() {
useEffect(() => {
init({
theme: {
primaryColor: '#2563EB',
secondaryColor: '#1D4ED8',
backgroundColor: '#FFFFFF',
headerBackgroundColor: '#F8FAFC',
headerTextColor: '#0F172A',
textColor: '#475569',
inputBackgroundColor: '#F1F5F9',
inputTextColor: '#0F172A',
inputPlaceholderColor: '#94A3B8',
borderRadius: '10px',
modalBorderRadius: '18px',
fontFamily: 'Inter, system-ui, -apple-system, Segoe UI, Roboto, sans-serif',
boxShadow: '0 20px 40px rgba(0,0,0,0.08)',
},
});
return () => destroy();
}, []);
return <button onClick={open}>Contact Us</button>;
}Vue 3
<script setup>
import { onMounted, onUnmounted } from 'vue';
import { init, open, destroy } from 'chatgram-widget';
onMounted(() => {
init({
theme: {
primaryColor: '#EE6055',
secondaryColor: '#D94F44',
},
});
});
onUnmounted(() => destroy());
</script>
<template>
<button @click="open">Contact Us</button>
</template>API Reference
| Method | Description |
|--------|-------------|
| init(config) | Initialize the widget. |
| open() | Open the modal |
| close() | Close the modal |
| destroy() | Remove widget from DOM entirely |
| updateTheme(theme) | Change colors at runtime |
| on(event, handler) | Subscribe to events |
| off(event, handler) | Unsubscribe |
| once(event, handler) | Subscribe once |
| isOpen() | Returns boolean |
| getState() | Returns 'idle' \| 'loading' \| 'success' \| 'error' \| 'uninitialized' |
| getVersion() | Returns version string |
Configuration
init({
publicKey: 'pk_abc123', // Optional — when provided, domain auto-detection is skipped
domain: 'mysite.com', // Default: auto-detected (skipped when publicKey is set)
apiBaseUrl: 'https://custom-api.com', // Default: chatgram-server.brandid.app
// Theme
theme: {
primaryColor: '#EE6055',
secondaryColor: '#D94F44',
backgroundColor: '#FFFFFF',
headerBackgroundColor: '#2C3345',
headerTextColor: '#FFFFFF',
textColor: '#6B7280',
inputBackgroundColor: '#1E2535',
inputTextColor: '#FFFFFF',
inputPlaceholderColor: '#9CA3AF',
borderRadius: '8px',
modalBorderRadius: '16px',
fontFamily: 'system-ui, sans-serif',
boxShadow: '0 25px 60px rgba(0,0,0,0.3)',
},
// Texts (i18n)
texts: {
title: 'Contact Support',
subtitle: "We're here to help you succeed",
submitButtonText: 'Submit',
successTitle: 'Message Sent!',
successMessage: "We've received your message and will get back to you via Email.",
errorMessage: 'Something went wrong.',
categories: { feedback: 'Feedback', bug: 'Bug', feature: 'Feature', support: 'Support' },
// ... see types.ts for all options
},
// Form behavior
maxMessageLength: 5000,
showTriggerButton: true,
triggerPosition: 'bottom-right',
triggerIconUrl: 'https://example.com/icon.png', // Custom trigger button icon
triggerBackgroundColor: '#EE6055', // Trigger button bg color (default: theme.primaryColor)
triggerTextColor: '#FFFFFF', // Trigger button icon color
triggerTooltip: 'Need Help?', // Trigger button tooltip text
showNameField: true,
zIndex: 999999,
closeOnOverlayClick: true,
closeOnEscape: true,
defaultEmail: '[email protected]', // Pre-fill
defaultName: 'John Doe', // Pre-fill
// Security
enableHoneypot: true,
minSubmitDelay: 2, // seconds — blocks bots that submit instantly
rateLimitMax: 3, // max submissions per window
rateLimitWindow: 300000, // 5 minutes
// Network
requestTimeout: 15000,
retryAttempts: 2,
// Auto-close
autoResetDelay: 0, // ms after success (0 = manual close)
// Callbacks
onSuccess: (data) => console.log('Ticket:', data.ticketId),
onError: (err) => console.error(err.code, err.message),
onOpen: () => {},
onClose: () => {},
onSubmit: (payload) => {
// Return false to cancel submission
return true;
},
});Events
import { on, off, once } from 'chatgram-widget';
on('open', () => analytics.track('widget_opened'));
on('close', () => {});
on('submit', (payload) => console.log('Submitting:', payload));
on('success', (data) => console.log('Ticket ID:', data.ticketId));
on('error', (err) => console.error(err.code)); // NETWORK_ERROR | TIMEOUT | SERVER_ERROR | etc.
on('state-change', (state) => {}); // idle | loading | success | error
on('theme-change', (theme) => {});
once('success', () => showConfetti()); // Fires once then auto-removesError Codes
| Code | Meaning |
|------|---------|
| NETWORK_ERROR | No internet or DNS failure |
| TIMEOUT | Request exceeded requestTimeout |
| SERVER_ERROR | 5xx from API |
| VALIDATION_ERROR | 4xx from API |
| RATE_LIMITED | 429 from API or client-side limit |
| HONEYPOT_TRIGGERED | Bot detected (silently faked success) |
| NOT_INITIALIZED | open() called before init() |
Security
| Protection | How |
|------------|-----|
| XSS | All user input rendered via textContent, never innerHTML. HTML entities escaped. |
| CSS Injection | Theme values sanitized — blocks expression(), url(), javascript:, data:. |
| CSS Isolation | Shadow DOM prevents leakage in both directions. |
| Spam (bots) | Honeypot hidden field — bots fill it, humans don't. Silently fakes success. |
| Spam (timing) | minSubmitDelay — rejects submissions faster than N seconds. |
| Spam (rate limit) | Sliding window rate limiter — max N submissions per window. |
| API safety | 15s timeout, AbortController, exponential backoff retry (only on 5xx/network). |
| Input validation | Email: RFC 5322. Subject/message: non-empty + length check. Newline injection blocked. |
Architecture
chatgram-widget/
├── src/
│ ├── core/
│ │ ├── types.ts # TypeScript interfaces, ChatgramError class
│ │ ├── config.ts # Defaults, deep merge, resolver, validation
│ │ ├── api.ts # HTTP client — timeout, retry, structured errors
│ │ ├── events.ts # Typed event emitter (on/off/once/emit)
│ │ └── rate-limiter.ts # Sliding window rate limiter
│ ├── ui/
│ │ ├── modal.ts # Modal renderer — Shadow DOM, focus trap, ARIA, states
│ │ ├── trigger.ts # Floating trigger button (Shadow DOM)
│ │ └── icons.ts # SVG icon set (trusted static markup)
│ ├── utils/
│ │ ├── sanitize.ts # XSS prevention, validation, CSS injection protection
│ │ └── dom.ts # Safe element creation, focus trap, scroll lock
│ ├── styles/
│ │ ├── widget-css.ts # CSS-in-JS for Shadow DOM injection
│ │ └── widget.css # External CSS entry point
│ └── index.ts # Public API (init/open/close/destroy/on/off)
├── dist/ # Build output
│ ├── chatgram-widget.esm.js
│ ├── chatgram-widget.cjs.js
│ ├── chatgram-widget.umd.js
│ ├── chatgram-widget.css
│ └── types/
├── package.json
├── tsconfig.json
└── rollup.config.mjsBrowser Support
Chrome 80+, Firefox 78+, Safari 14+, Edge 80+
License
MIT — Built by brandID
