aivy-chat-react
v0.4.2
Published
Aivy customizable chat widget for web channels
Readme
aivy-chat-react
A customizable React chat widget for Aivy web channels. Drop it into any React/Next.js app, connect your Aivy channel, and start chatting with visitors.
Install
npm install aivy-chat-react
# or
yarn add aivy-chat-react
# or
pnpm add aivy-chat-reactPeer dependencies:
npm install react react-dom lucide-reactQuick start
import { ChatWidget } from 'aivy-chat-react';
import 'aivy-chat-react/styles.css';
export default function App() {
return (
<ChatWidget
channelId="your-channel-id"
url="https://api.aivygrow.ai/api/v1"
title="Support"
subtitle="We reply fast"
/>
);
}Note:
urlis the API base URL including the/api/v1prefix (e.g.https://api.aivygrow.ai/api/v1orhttp://localhost:3000/api/v1in local dev). Every request (send, upload, history) goes to${url}/channels/website/....
The pre-chat form is enabled by default. It asks for a required name, email, and phone before the first message. It is skipped only when all three values are already available through user, visitor props, or persisted visitor data; otherwise known values are prefilled and the missing fields remain visible. A random visitorId is generated automatically as web-{uuid} and stored locally as a technical identifier; it is not used as the captured lead phone.
<ChatWidget
channelId="your-channel-id"
url="https://api.aivygrow.ai/api/v1"
enablePreChatForm
preChatFormVariant="bordered"
/>enablePreChatForm defaults to true and preChatFormVariant defaults to 'bordered'. To force the form to appear during testing, omit at least one visitor contact field or clear the visitor storage selected by persistConversation.
Importing styles
Import the CSS once in your app entry:
import 'aivy-chat-react/styles.css';Layout modes
Floating (default)
<ChatWidget
channelId="..."
url="https://api.aivygrow.ai/api/v1"
position="bottom-right"
width={380}
height={560}
/>Inline / embedded
<ChatWidget
channelId="..."
url="https://api.aivygrow.ai/api/v1"
position="inline"
defaultOpen
width="100%"
height={480}
/>Side panel
<ChatWidget
channelId="..."
url="https://api.aivygrow.ai/api/v1"
position="bottom-right"
sidePanel
sidePanelWidth={380}
defaultOpen
/>Themes and presets
Start from a ready-made visual system with themePreset:
<ChatWidget
channelId="..."
url="https://api.aivygrow.ai/api/v1"
themePreset="apple" // "apple" | "typebot" | "whatsapp"
/>Each preset includes light and dark palettes:
<ChatWidget themePreset="whatsapp" themeMode="dark" {...connectionProps} />Every preset styles the complete experience: panel, header, conversation, bubbles,
composer, launcher and pre-chat form. Pass a theme object to override only the
tokens you want while retaining the rest of the preset.
<ChatWidget
channelId="..."
url="https://api.aivygrow.ai/api/v1"
themePreset="apple"
theme={{
primary: '#0d9488',
headerBackground: '#0f766e',
launcherBackground: '#0d9488',
panelBackground: 'rgba(255, 255, 255, 0.82)',
conversationBackground: '#f0fdfa',
user: '#0d9488',
agent: '#f1f5f9',
radius: '1.5rem',
bubbleRadius: '1rem',
inputRadius: '999px',
panelBlur: '24px',
}}
/>The built-in definitions are exported if you need to derive a reusable theme:
import { THEME_PRESETS, type WidgetTheme } from 'aivy-chat-react';
const brandTheme: WidgetTheme = {
...THEME_PRESETS.typebot,
primary: '#ea580c',
user: '#ea580c',
launcherBackground: '#ea580c',
};Available visual tokens include the existing palette values plus
panelBackground, conversationBackground, panelBorder, headerBorder,
inputBorder, inputPlaceholder, bubbleRadius, inputRadius,
launcherRadius, panelBlur, headerBlur, messageMaxWidth, and
launcherShadow, launcherBorder, launcherBorderWidth, and
launcherHoverScale. Typography can be controlled with fontFamily,
baseFontSize, messageFontSize, headerTitleSize, headerSubtitleSize, and
inputFontSize. Internal quick replies and card actions use
actionBackground, actionForeground, actionBorder, and actionRadius.
The outer border is disabled by default and can be enabled with
panelBorderWidth and panelBorder. Use the existing launcherSize, launcherIcon,
launcherGreeting, showLauncherGreeting, position, and offset props for
the button's content and placement. Component render props and *ClassName escape hatches remain
available for fully custom structures.
Common props
| Prop | Description |
| --- | --- |
| channelId | Your Aivy web channel ID |
| url | Your Aivy API base URL, e.g. https://api.aivygrow.ai/api/v1 |
| title | Header title |
| subtitle | Header subtitle |
| position | bottom-right, bottom-left, top-right, top-left, inline |
| sidePanel | Render as full-height side panel |
| width / height | Widget dimensions |
| defaultOpen | Open the widget by default |
| onOpenChange | Called when the widget opens/closes |
| onClose | Called when the widget is closed |
| resetVisitorOnClose | Regenerate visitorId and clear the conversation when the widget closes |
| resetConversationOnClose | Clear the conversation when the widget closes (without changing visitorId) |
| persistConversation | true (localStorage, shared across windows), 'session' (per-tab), false (no persistence) |
| onVisitorIdChange | Called when the visitorId changes |
| quickActions | Array of quick reply buttons |
| features | { attachments, multipleAttachments, voice, quickActions, typingIndicator, unreadBadge, ... } |
| acceptedFileTypes | HTML accept value for file input, e.g. image/*,.pdf |
| maxFileSizeMB | Max size for each uploaded file |
| maxFiles | Max number of files per message |
| fileRestrictions | Object with maxFileSizeMB, maxFiles, acceptedFileTypes |
| onFileRejected | Called when a file is rejected (too large, too many, invalid type) |
| onError | Called on send/upload errors |
| onMessageSent | Called after a message is confirmed by the server |
| onMessageReceived | Called for every new message received from the agent |
| onQuickReply | Called when a quick reply button is tapped |
| onCardButton | Called when a card or carousel button is tapped |
Persistence (conversation & visitor name)
The conversation id and the visitor name are stored per channel (aivy_conversation_id_{channelId}), so multiple widgets or sites never share a conversation. Control the behavior with persistConversation:
| Value | Storage | Behavior |
| --- | --- | --- |
| true (default) | localStorage | Shared across windows/tabs of the same browser |
| 'session' | sessionStorage | Only this tab — a new window starts a fresh chat |
| false | none | New conversation on every reload |
<ChatWidget channelId="..." url="..." persistConversation="session" />The visitor's name (from the pre-chat form or visitorName) is remembered with the same mode, so returning visitors are not asked again and messages always arrive with their name.
Polling
New messages are fetched with lightweight polling that only runs while the widget is open and the browser tab is visible. A closed widget makes zero requests. Tune the interval with pollInterval (default 2000 ms) or disable it completely:
<ChatWidget channelId="..." url="..." pollInterval={8000} enablePolling={false} />Close callbacks
Run your own logic when the visitor closes the widget, and optionally reset the visitor:
<ChatWidget
channelId="..."
url="..."
onClose={() => console.log('widget closed')}
onVisitorIdChange={(id) => console.log('new visitorId:', id)}
resetVisitorOnClose
/>When resetVisitorOnClose is enabled, the widget will generate a new visitorId and clear the local conversation every time it closes. Use resetConversationOnClose if you only want to clear the conversation without changing the visitor.
Attachments
Images, documents, audio and video are supported. Select one or multiple files depending on the features.multipleAttachments flag (default true):
<ChatWidget
channelId="..."
url="..."
features={{ attachments: true, multipleAttachments: true }}
acceptedFileTypes="image/*,.pdf,.doc,.docx"
maxFileSizeMB={10}
maxFiles={5}
onFileRejected={(rejection) => console.warn(rejection.message, rejection.file)}
/>Rich messages
The widget renders rich messages received from the agent: product/service catalogs, interactive lists, order receipts, carousels, cards, quick replies, locations, templates and events. It also exposes callbacks for interactive elements:
<ChatWidget
channelId="..."
url="..."
onQuickReply={(reply) => console.log('quick reply:', reply)}
onCardButton={(button, card) => console.log('card button:', button, card)}
/>By default, tapping a quick reply, a catalog item, a list row or a postback card button will send a new message with the item title as content.
Catalog & list
Catalog messages render as horizontally scrollable product/service cards (image, price, description). List messages render WhatsApp-style sections with tappable rows.
Order receipts
When the agent registers an order, the widget shows a receipt card with the order reference, status, itemized products (quantities, notes, prices), total, delivery info and payment methods. Updates to an existing order render the same card with newly added items highlighted.
Media bubbles
Images and videos render full-bleed inside the bubble (WhatsApp style), with multi-image grids and linkified URLs in text. Voice notes use a waveform player with real audio peaks, drag-to-seek and 1x/1.5x/2x playback speed.
New conversation
The header includes a "new conversation" button that closes the current conversation server-side and starts a fresh one on the next message. Customize its label with locale.newConversation.
Sending multiple messages
The widget uses an internal queue to send messages one by one. If the visitor sends several messages quickly, they are enqueued and sent sequentially, so the agent can process them in order.
<ChatWidget
channelId="..."
url="..."
/>Custom upload handler
If you need to upload files to your own storage or add extra metadata, provide a custom uploadFile function:
<ChatWidget
channelId="..."
url="..."
uploadFile={async (file) => {
const formData = new FormData();
formData.append('file', file);
const res = await fetch('/api/upload', { method: 'POST', body: formData });
const data = await res.json();
return { url: data.url, mediaType: 'image', name: file.name };
}}
/>Pre-chat form
By default, the widget does not ask for the visitor's name. Enable it or prefill the data:
<ChatWidget
channelId="..."
url="..."
visitorName="John Doe"
enablePreChatForm
/>Locale
Override any label:
<ChatWidget
locale={{
title: 'Habla con nosotros',
placeholder: 'Escribe un mensaje...',
}}
/>License
MIT
