Botmara Chat Widget
A tiny, framework-agnostic chat widget (<botmara-chat>) with React/Vue wrappers. Connect your API key and pass your own data as a knowledge base (KB). Customize the look (accent, radius, size), show a floating bubble or inline window, and wire optional notifications. Default upstream is https://api.botmara.com
We are working on allowing outside API key integrations on later versions.
Get an API KeyInstall
Install exactly one wrapper for your stack. You don’t need to add @botmara/core directly when using the wrappers.
npm i @botmara/react
# or: pnpm add @botmara/react
yarn add @botmara/reactnpm i @botmara/vue
# or: pnpm add @botmara/vue
yarn add @botmara/vue<script type="module" src="https://unpkg.com/@botmara/core/dist/botmara-chat.js"></script>
<botmara-chat bot-name="Helper"></botmara-chat>Quick Start
Usage — ReactJS
Import the React wrapper and drop the component. If using Next.js, see notes below.
import { BotmaraChat } from "@botmara/react";
export default function Bot(){
return (
<BotmaraChat
mode="bubble" // "bubble" | "inline"
chipBg ="orange"
position="bottom-right" // or "bottom-left"
color="#22c55e"
radius={16}
botName="Support"
botDescription="Ask about pricing, shipping, or policies."
sections={[
{ id:"pricing", title:"Pricing", content:"Free 1k/mo; Pro $19/mo" },
{ id:"shipping", title:"Shipping", content:"3–5 days; 30-day returns" },
]}
kb={{ opening_hours: "8am-10pm", smoking-policy: "No smoking allowed" }}
/>
);
}Usage — Nextjs
Always use a client component and dynamic import with ssr:false in Next.js.
"use client";
import dynamic from "next/dynamic";
const BotmaraChat = dynamic(() => import("@botmara/react").then(m => m.BotmaraChat), { ssr: false });
export default function Bot(){
return (
<BotmaraChat
mode="bubble" // "bubble" | "inline"
position="bottom-right" // or "bottom-left"
color="#22c55e"
radius={16}
botName="Support"
botDescription="Ask about pricing, shipping, or policies."
sections={[
{ id:"pricing", title:"Pricing", content:"Free 1k/mo; Pro $19/mo" },
{ id:"shipping", title:"Shipping", content:"3–5 days; 30-day returns" },
]}
kb={{ opening_hours: "8am-10pm", pet-policy: "No pets allowed" }}
/>
);
}Usage — Vue
Import the Vue wrapper and drop the component. Vite works out of the box.
<script setup lang="ts">
import { BotmaraChat } from "@botmara/vue";
</script>
<template>
<BotmaraChat
mode="inline"
botName="Support"
:sections="[
{ id:'faq', title:'FAQ', content:'Ask me about returns, billing, or features.' }
]"
:kb="{ faq: 'Returns 30 days. Billing via Stripe. Features: KB + sections.' }"
/>
</template>
Usage — HTML
<head>
<meta charset="utf-8" />
<title>BotMara – Demo</title>
<script type="module" src="https://cdn.jsdelivr.net/npm/@botmara/core/dist/botmara-chat.js" defer></script>
</head>
<body>
<botmara-chat
bot-name="Helper"
color="#0ea5e9"
sections='[{ "id":"faq", "title":"FAQ", "content":"Ask me about orders and refunds."}]'
kb='{"faq":"Orders ship in 2–3 days. Refunds within 7 days."}'
></botmara-chat>
</body>
</html>
Props & Attributes
React/Vue props map 1:1 to the web component attributes below. In plain HTML, boolean flags are presence-based (e.g. notify-sound).
| Attribute | React/Vue Prop | Type | Default | Notes |
|---|---|---|---|---|
mode | mode | "bubble" | "inline" | bubble | Floating bubble or inline panel |
position | position | "bottom-right" | "bottom-left" | bottom-right | Bubble + window corner |
size | size | "sm" | "md" | "lg" | "xl" | md | Preset width/height |
width | width | number | string | — | Overrides --bm-width (px or CSS size) |
height | height | number | string | — | Overrides --bm-height |
radius | radius | number | string | 12px | Sets --bm-radius (px or CSS size) |
placeholder | placeholder | string | Type a message… | Input placeholder |
fallback-message | fallbackMessage | string | “Sorry, I don’t have that yet. Please contact support.” | Shown on errors/missing reply |
bot-name | botName | string | Assistant | Header title |
bot-description | botDescription | string | How can I help you today? | Header subtitle |
| Attribute | React/Vue Prop | Type | Default | Notes |
|---|---|---|---|---|
color | color | string | #4f46e5 | Accent color (sets --botmara-accent) |
bg | bg | string | #ffffff | Panel background (--botmara-bg) |
text | text | string | #111827 | Main text (--botmara-text) |
text-light | textLight | string | #6b7280 | Secondary text (--botmara-text-light) |
user-bg | userBg | string | accent | User bubble bg (--botmara-user-bg) |
user-text | userText | string | #fff | User bubble text (--botmara-user-text) |
assistant-bg | assistantBg | string | #f3f4f6 | Assistant bubble bg (--botmara-assistant-bg) |
assistant-text | assistantText | string | text | Assistant bubble text (--botmara-assistant-text) |
chip-bg | chipBg | string | accent | Chip background color (--botmara-chip-bg) |
chip-light-text | chipLightText | string | #fff | Chip light text color (--botmara-chip-light-text) |
| Attribute | React/Vue Prop | Type | Default | Notes |
|---|---|---|---|---|
sections | sections | Section[] | [] | Clickable chips → content/prompt/autoSend |
sections-mode | sectionsMode | "merge" | "replace" | merge | Merge/replace vs KB-derived chips |
data | data | Record<string,string> | {} | Alias for kb/knowledge |
knowledge | knowledge | Record<string,string> | {} | Alias for kb |
kb | kb | Record<string,string> | {} | Small map used to seed/answer/refine |
data-sections | dataSections | Section[] | stringified JSON | — | Alias for sections (HTML attr convenience) |
| Attribute | React/Vue Prop | Type | Default | Notes |
|---|---|---|---|---|
endpoint | endpoint | string | apiBase + /v1/chat | Full URL or your /api proxy (⚒️ V0.0.3 coming soon) |
api-base | apiBase | string | https://api.botmara.com | Default upstream base; change anytime (V0.03 coming soon ⚒️ ) |
api-key | apiKey | string | — | Only for direct-to-upstream (avoid in public clients) |
auth-type | authType | "x-api-key" | "bearer" | x-api-key | Header style when apiKey is present |
| Attribute | React/Vue Prop | Type | Default | Notes |
|---|---|---|---|---|
notify-sound | notifySound | boolean | false | Ping on assistant reply (needs user gesture) |
notify-sound-open | notifySoundOpen | boolean | false | Allow sound even when window is focused |
notify-browser | notifyBrowser | boolean | false | Use Notification API when granted |
notify-toast | notifyToast | boolean | false | Soft toast near bubble |
mute | mute | boolean | false | Disable vibration/sound |
Defaults (implementation): apiBase = https://api.botmara.com, path /v1/chat, size md, radius 12px, accent #4f46e5.
Theming & Size
Quick props cover most needs. You can also set CSS variables directly on the element.
botmara-chat {
--botmara-accent: #10b981; /* same as color prop */
--botmara-bg: #fff;
--botmara-text: #111827;
--botmara-text-light: #6b7280;
--botmara-user-bg: #10b981;
--botmara-user-text: #fff;
--botmara-assistant-bg: #f3f4f6;
--botmara-assistant-text: #111827;
--bm-radius: 14px;
--bm-width: 420px;
--bm-height: 640px;
}
Sections & KB
Sections are chips. If a section has content, it’s sent (and may be refined). If it has prompt, it pre-fills the input (optionally autoSend). If neither, the widget tries kb[section.id].
<BotmaraChat
sections=[
{ id: "pricing", title: "Pricing", content: "Free 1k; Pro $19/mo; Scale" },
{ id: "shipping", title: "Shipping", prompt: "Do you ship to EU?" },
]
kb={{ shipping: "Domestic 3–5 days; Intl 5–10 days." }}
sectionsMode="merge" // or "replace"
/>Notifications
Opt-in via presence attributes. Audio needs a user gesture; browser notifications require permission.
<botmara-chat notify-sound notify-toast notify-browser></botmara-chat>Events
The element emits send, response, error, sectionchange. In React, attach via a ref.
"use client";
import { useEffect, useRef } from "react";
import dynamic from "next/dynamic";
const BotmaraChat = dynamic(() => import("@botmara/react").then(m => m.BotmaraChat), { ssr:false });
export default function WithEvents(){
const ref = useRef<any>(null);
useEffect(() => {
const el = ref.current?.shadowRoot?.host ?? ref.current;
if (!el) return;
const onSend = (e: any) => console.log('send', e.detail); // { text, section, sessionId }
const onResp = (e: any) => console.log('response', e.detail); // { answer, refined?, source?, ... }
const onErr = (e: any) => console.warn('error', e.detail); // { code, message, type?, retryAfter?, headers? }
const onSec = (e: any) => console.log('sectionchange', e.detail); // { section }
el.addEventListener('send', onSend);
el.addEventListener('response', onResp);
el.addEventListener('error', onErr);
el.addEventListener('sectionchange', onSec);
return () => {
el.removeEventListener('send', onSend);
el.removeEventListener('response', onResp);
el.removeEventListener('error', onErr);
el.removeEventListener('sectionchange', onSec);
};
}, []);
return <BotmaraChat ref={ref} endpoint="/api/botmara/chat" />;
}SSR & Framework Notes
- Web components need the DOM; in Next.js use
dynamic()with{ ssr: false }. - Don’t import the core script on the server; only on the client (or via React/Vue wrappers).
- Keep secrets off the client. The widget’s
apiKeyprop is for internal demos only.
Troubleshooting
Wrap the import with Next.js dynamic and ssr:false:
const BotmaraChat = dynamic(() => import("@botmara/react").then(m => m.BotmaraChat), { ssr:false });The core web component targets the browser. Use wrappers on the client side, or run it only after DOM is available.Check your proxy and API key. Surface a friendly fallbackMessage and back off on 429s.FAQ
No — install only the wrapper for your stack. The wrapper brings the core under the hood.
Not yet. This is on our road map to allow you to Point endpoint at any compatible API or change apiBase.
Keep it small on the free tier (a few hundred key-value pairs). Larger KBs require a paid plan as our servers would require more memory to process them.