@maxischmaxi/bootai-react
v0.2.0
Published
Drop-in AI chat widget for React apps and static sites. Ships as both a React component (`ChatBot`) and a standalone IIFE script (`widget.global.js`) for use without a build step.
Downloads
412
Readme
@maxischmaxi/bootai-react
Drop-in AI chat widget for React apps and static sites. Ships as both a React component (ChatBot) and a standalone IIFE script (widget.global.js) for use without a build step.
Installation
# npm
npm install @maxischmaxi/bootai-react
# pnpm
pnpm add @maxischmaxi/bootai-react
# yarn
yarn add @maxischmaxi/bootai-reactPeer dependencies: react >=18 and react-dom >=18.
Quick Start
React
import { ChatBot } from "@maxischmaxi/bootai-react";
function App() {
return (
<ChatBot
siteId="your-site-id"
apiUrl="https://api.example.com"
consentGiven={true}
/>
);
}Script Tag (no build step)
<script src="https://cdn.example.com/widget.global.js"></script>
<script>
BootAI.render({
siteId: "your-site-id",
apiUrl: "https://api.example.com",
consentGiven: true,
});
</script>Props
ChatBotProps
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| siteId | string | required | Unique identifier for your site. |
| apiUrl | string | required | Base URL of the BootAI chat API. |
| theme | "light" \| "dark" \| "auto" | "light" | Color theme. "auto" follows prefers-color-scheme. |
| display | "floating" \| "inline" | "floating" | Floating shows a FAB + popup panel. Inline renders the chat directly in the DOM flow. |
| consentGiven | boolean \| ConsentConfig | false | Controls whether the chat is active. See Consent. |
| classNames | ChatBotClassNames | — | CSS class names per UI slot. See Custom Styling. |
| styles | ChatBotStyles | — | Inline style overrides per UI slot. See Custom Styling. |
| renderCallbacks | ChatBotRenderCallbacks | — | Replace any UI section with a custom render function. See Render Callbacks. |
Consent
The widget renders nothing until consent is granted. There are two ways to provide consent:
Simple boolean
Pass consentGiven={true} when your app already manages consent externally.
<ChatBot siteId="..." apiUrl="..." consentGiven={true} />Consent config (built-in banner)
Pass a ConsentConfig object to show a built-in consent banner before the chat loads.
<ChatBot
siteId="..."
apiUrl="..."
consentGiven={{
text: "This chat is operated by a third party. By using it you agree to data processing.",
privacyUrl: "https://example.com/privacy",
acceptLabel: "Start Chat", // optional, default: "Akzeptieren"
storage: "localStorage", // optional, default: "sessionStorage"
}}
/>ConsentConfig
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| text | string | required | Consent banner message. |
| privacyUrl | string | required | Link to your privacy policy. |
| acceptLabel | string | "Akzeptieren" | Label for the accept button. |
| storage | "localStorage" \| "sessionStorage" \| "none" | "sessionStorage" | Where to persist the consent decision. "none" means the banner appears on every visit. |
Display Modes
Floating (default)
A floating action button (FAB) in the bottom-right corner. Clicking it opens/closes the chat panel as a popup.
<ChatBot siteId="..." apiUrl="..." display="floating" consentGiven={true} />Inline
The chat panel renders directly inside the parent element, like a regular component. No FAB, no popup.
<ChatBot siteId="..." apiUrl="..." display="inline" consentGiven={true} />Theming
Three theme modes are available:
| Value | Behavior |
|-------|----------|
| "light" | Light color scheme (white background, dark text). |
| "dark" | Dark color scheme (dark background, light text). |
| "auto" | Follows the user's system preference via prefers-color-scheme. |
The active theme is exposed on the root element as data-bootai-theme.
Colors Object
Both themes resolve to a Colors object that is passed to all render callbacks:
interface Colors {
bg: string; // main background
surface: string; // secondary surface (e.g. input area)
border: string; // border color
text: string; // primary text color
muted: string; // secondary text color
userBg: string; // user message bubble background
assistantBg: string; // assistant message bubble background
inputBg: string; // input field background
inputBorder: string; // input field border
}Custom Styling
Every UI element in the widget is identified by a slot key. You can target any slot with CSS class names, inline style overrides, or both.
Slot Keys
| Slot | Element |
|------|---------|
| consentBanner | Consent banner container |
| consentText | Consent text paragraph |
| consentPrivacyLink | Privacy policy link |
| consentAcceptButton | Accept/start button |
| floatingRoot | Floating mode root (fixed position container) |
| floatingPanel | Floating chat panel (popup) |
| fab | Floating action button |
| fabIcon | Icon inside the FAB |
| inlineRoot | Inline mode root container |
| header | Chat header bar |
| headerStatusDot | Green status indicator dot |
| headerTitle | Header title text |
| headerSubtitle | Header subtitle text |
| messagesContainer | Scrollable messages area |
| emptyState | "How can I help?" placeholder |
| userMessage | User message bubble |
| assistantMessage | Assistant message bubble |
| loadingIndicator | Typing indicator (dots) |
| inputContainer | Input area wrapper |
| input | Text input field |
| sendButton | Send button |
classNames — CSS Classes
Add CSS class names to any slot. Useful for Tailwind CSS, CSS Modules, or plain stylesheets.
<ChatBot
siteId="..."
apiUrl="..."
consentGiven={true}
classNames={{
header: "my-chat-header",
userMessage: "my-user-bubble",
sendButton: "my-send-btn",
}}
/>styles — Inline Style Overrides
Override default inline styles per slot. Your styles are merged on top of the defaults (spread after), so they take precedence.
<ChatBot
siteId="..."
apiUrl="..."
consentGiven={true}
styles={{
header: { background: "#92400e" },
headerStatusDot: { backgroundColor: "#fbbf24" },
sendButton: { background: "#92400e" },
inlineRoot: { borderColor: "#d97706", maxHeight: 600 },
}}
/>Combining classNames and styles
Both can be used simultaneously on the same slot. The className is applied to the element, and styles are merged into the style attribute.
<ChatBot
siteId="..."
apiUrl="..."
consentGiven={true}
classNames={{ header: "shadow-lg rounded-t-xl" }}
styles={{ header: { background: "linear-gradient(135deg, #667eea, #764ba2)" } }}
/>Render Callbacks
For full control over a UI section, provide a render callback. When a render callback is set, it completely replaces the default rendering for that section. Each callback receives contextual props and must return ReactNode.
ChatBotRenderCallbacks
interface ChatBotRenderCallbacks {
renderConsentBanner?: (props: ConsentBannerRenderProps) => ReactNode;
renderHeader?: (props: HeaderRenderProps) => ReactNode;
renderMessage?: (props: MessageRenderProps) => ReactNode;
renderEmptyState?: (props: EmptyStateRenderProps) => ReactNode;
renderLoadingIndicator?: (props: LoadingIndicatorRenderProps) => ReactNode;
renderInput?: (props: InputRenderProps) => ReactNode;
renderFab?: (props: FabRenderProps) => ReactNode;
}Callback Props Reference
ConsentBannerRenderProps
| Prop | Type | Description |
|------|------|-------------|
| isDark | boolean | Whether dark mode is active. |
| display | "floating" \| "inline" | Current display mode. |
| text | string | Consent text from config. |
| privacyUrl | string | Privacy policy URL. |
| acceptLabel | string | Accept button label. |
| onAccept | () => void | Must be called to grant consent and persist the decision. |
HeaderRenderProps
| Prop | Type | Description |
|------|------|-------------|
| colors | Colors | Active theme colors. |
MessageRenderProps
| Prop | Type | Description |
|------|------|-------------|
| message | Message | The message object ({ role, content }). |
| colors | Colors | Active theme colors. |
| renderedContent | ReactNode | Pre-rendered content with parsed links. Use this to keep default link rendering. |
EmptyStateRenderProps
| Prop | Type | Description |
|------|------|-------------|
| colors | Colors | Active theme colors. |
LoadingIndicatorRenderProps
| Prop | Type | Description |
|------|------|-------------|
| colors | Colors | Active theme colors. |
InputRenderProps
| Prop | Type | Description |
|------|------|-------------|
| value | string | Current input value. |
| isLoading | boolean | Whether a response is streaming. |
| colors | Colors | Active theme colors. |
| onChange | (value: string) => void | Update the input value. |
| onSend | () => void | Send the current message. |
| inputRef | RefObject<HTMLInputElement \| null> | Ref for auto-focus behavior. Attach to your <input> element. |
FabRenderProps
| Prop | Type | Description |
|------|------|-------------|
| isOpen | boolean | Whether the chat panel is open. |
| onToggle | () => void | Toggle the chat panel. |
Examples
Custom header with branding
<ChatBot
siteId="..."
apiUrl="..."
consentGiven={true}
renderCallbacks={{
renderHeader: ({ colors }) => (
<div style={{ padding: "16px 20px", background: "#92400e", color: "#fff", display: "flex", alignItems: "center", gap: 10 }}>
<img src="/logo.svg" alt="" width={24} height={24} />
<div>
<div style={{ fontWeight: 600, fontSize: 14 }}>My Store Support</div>
<div style={{ fontSize: 12, opacity: 0.85 }}>We typically reply instantly</div>
</div>
</div>
),
}}
/>Custom empty state
renderCallbacks={{
renderEmptyState: ({ colors }) => (
<div style={{ padding: 40, textAlign: "center", color: colors.muted }}>
<p style={{ fontSize: 24 }}>👋</p>
<p>Ask me anything about our products!</p>
</div>
),
}}Custom message rendering
renderCallbacks={{
renderMessage: ({ message, colors, renderedContent }) => (
<div style={{
display: "flex",
justifyContent: message.role === "user" ? "flex-end" : "flex-start",
}}>
{message.role === "assistant" && <img src="/bot-avatar.png" width={28} height={28} />}
<div style={{
padding: "10px 14px",
borderRadius: 12,
background: message.role === "user" ? colors.userBg : colors.assistantBg,
color: message.role === "user" ? "#fff" : colors.text,
maxWidth: "75%",
}}>
{renderedContent}
</div>
</div>
),
}}Custom FAB
renderCallbacks={{
renderFab: ({ isOpen, onToggle }) => (
<button
onClick={onToggle}
style={{
width: 60, height: 60, borderRadius: "50%",
background: isOpen ? "#ef4444" : "#10b981",
color: "#fff", border: "none", cursor: "pointer",
fontSize: 24,
}}
>
{isOpen ? "✕" : "💬"}
</button>
),
}}Widget Script (IIFE)
The package also ships a standalone script at dist/widget.global.js that bundles React internally. Use it on any HTML page without a build step.
Loading
<script src="https://cdn.example.com/widget.global.js"></script>This exposes a global BootAI object with two methods:
BootAI.render(options)
Renders the chat widget. Accepts all the same options as the React component props.
BootAI.render({
siteId: "your-site-id",
apiUrl: "https://api.example.com",
theme: "auto",
display: "floating",
consentGiven: {
text: "We use a chat assistant powered by AI.",
privacyUrl: "/privacy",
acceptLabel: "Got it",
storage: "localStorage",
},
classNames: {
header: "my-header-class",
},
styles: {
header: { background: "#1e3a5f" },
sendButton: { background: "#1e3a5f" },
},
});BootAI.destroy()
Unmounts the widget and removes its DOM container.
BootAI.destroy();Render Callbacks in the Widget Script
Since widget users don't have React, render callbacks return HTMLElement or string (HTML) instead of ReactNode. The widget internally bridges these to React using an HtmlBridge component.
BootAI.render({
siteId: "...",
apiUrl: "...",
consentGiven: true,
renderCallbacks: {
// Return an HTML string
renderHeader: function (props) {
return '<div style="padding:16px 20px;background:#1e3a5f;color:#fff">' +
'<b>My Custom Header</b></div>';
},
// Or return a DOM element
renderEmptyState: function (props) {
var div = document.createElement("div");
div.style.padding = "40px";
div.style.textAlign = "center";
div.style.color = props.colors.muted;
div.textContent = "How can we help you today?";
return div;
},
},
});The callback props are identical to the React version. The only difference is the return type: HTMLElement | string instead of ReactNode.
Web Component
The package ships a custom element at dist/web-component.global.js that bundles React internally. Use it on any HTML page with a single <script> tag — no build step, no JS required.
Loading
<script src="https://cdn.example.com/web-component.global.js"></script>This registers the <bootai-chat> custom element.
HTML Attributes
Simple string/boolean values are set via HTML attributes:
| Attribute | Type | Description |
|-----------|------|-------------|
| site-id | string | Required. Your site identifier. |
| api-url | string | Base URL of the BootAI chat API. |
| theme | "light" \| "dark" \| "auto" | Color theme. Default: "light". |
| display | "floating" \| "inline" | Display mode. Default: "floating". |
| consent-given | "true" | Set to "true" to skip the consent banner. |
JS Properties
For complex data (objects, callbacks), set properties via JavaScript:
| Property | Type | Description |
|----------|------|-------------|
| consentGiven | boolean \| ConsentConfig | Consent config object or boolean. |
| classNames | ChatBotClassNames | CSS class overrides per slot. |
| styles | ChatBotStyles | Inline style overrides per slot. |
| renderCallbacks | WidgetRenderCallbacks | Custom render functions (return HTMLElement \| string). |
Example
<script src="https://cdn.example.com/web-component.global.js"></script>
<!-- Simple usage with HTML attributes -->
<bootai-chat
site-id="your-site-id"
api-url="https://api.example.com"
theme="auto"
consent-given="true"
></bootai-chat>
<!-- Advanced usage with JS properties -->
<bootai-chat
site-id="your-site-id"
api-url="https://api.example.com"
display="inline"
></bootai-chat>
<script>
const chat = document.querySelector("bootai-chat");
chat.consentGiven = {
text: "This chat uses AI. Do you agree?",
privacyUrl: "/privacy",
acceptLabel: "Accept",
storage: "localStorage",
};
chat.styles = {
header: { background: "#1e3a5f" },
sendButton: { background: "#1e3a5f" },
};
</script>TypeScript
All types are exported from the package entry point:
import type {
ChatBotProps,
ConsentConfig,
ChatBotSlot,
ChatBotClassNames,
ChatBotStyles,
ChatBotRenderCallbacks,
Message,
Colors,
ConsentBannerRenderProps,
HeaderRenderProps,
MessageRenderProps,
EmptyStateRenderProps,
LoadingIndicatorRenderProps,
InputRenderProps,
FabRenderProps,
} from "@maxischmaxi/bootai-react";Key Types
// All targetable UI slots
type ChatBotSlot =
| "consentBanner" | "consentText" | "consentPrivacyLink" | "consentAcceptButton"
| "floatingRoot" | "floatingPanel" | "fab" | "fabIcon"
| "inlineRoot"
| "header" | "headerStatusDot" | "headerTitle" | "headerSubtitle"
| "messagesContainer" | "emptyState" | "userMessage" | "assistantMessage" | "loadingIndicator"
| "inputContainer" | "input" | "sendButton";
// CSS class name overrides
type ChatBotClassNames = Partial<Record<ChatBotSlot, string>>;
// Inline style overrides
type ChatBotStyles = Partial<Record<ChatBotSlot, React.CSSProperties>>;
// Chat message
interface Message {
role: "user" | "assistant";
content: string;
}How It Works
- Consent gate — The widget renders nothing until
consentGivenis truthy. If aConsentConfigis passed, a banner is shown first. - Readiness check — After consent, the widget calls
GET {apiUrl}/chat/ready?siteId={siteId}. It only renders the chat UI when the API responds{ ready: true }. - Streaming responses — Messages are sent via
POST {apiUrl}/chatand responses are streamed usingReadableStream. The assistant's reply appears token by token. - Link detection — Assistant messages automatically render Markdown-style links (
[text](url)) and bare URLs as clickable<a>tags. - Hydration-safe — The widget defers rendering until after client-side mount to prevent hydration mismatches in SSR frameworks (Next.js, Remix, etc.).
API Contract
GET /chat/ready?siteId={siteId}
Returns { ready: boolean }. The widget polls this once after consent.
POST /chat
Request body:
{
"siteId": "your-site-id",
"message": "user's message",
"history": [
{ "role": "user", "content": "previous message" },
{ "role": "assistant", "content": "previous response" }
]
}Response: streaming text body. An internal ---SOURCES_END---\n marker separates metadata from the visible response.
Build Outputs
| File | Format | Description |
|------|--------|-------------|
| dist/index.js | ESM | React component (tree-shakeable). react and react-dom are external. |
| dist/index.cjs | CJS | React component for CommonJS environments. |
| dist/index.d.ts | TypeScript | Type declarations. |
| dist/widget.global.js | IIFE | Standalone script with React bundled. Exposes BootAI global. |
| dist/web-component.global.js | IIFE | Custom element <bootai-chat> with React bundled. |
License
See repository root for license information.
