React component reference
Package: @bootstrapware/chat
Styles: @bootstrapware/chat/styles.css
Import styles once near the feature. Use a Client Component in Next.js App Router. Bootstrapware does not authenticate chat end users. Pass user from your session.
Props
| Prop | Type | Required | Notes |
|---|---|---|---|
| user | ChatAuthor | null | yes | Opaque stable id. Optional name, email, avatarUrl for display. |
| adapter | ChatAdapter | BYO / local | If set, wins over Hosted. Use createLocalAdapter or your own API. |
| appId | string | Hosted / BYO config | With publishableKey for Hosted. Prefix cha_. |
| publishableKey | string | Hosted | Browser-safe. Never put secret keys in the client. |
| apiBaseUrl | string | no | Defaults to https://chat.bootstrapware.co |
| config | Partial<ChatAppConfig> | no | Overrides toggles / empty state until Hosted config loads. emptyState is only for a truly empty inbox; search misses have their own copy. |
| authorToken | string | no | Short-lived host-signed assertion from POST /api/v1/author-tokens (secret key). Required when the app sets requireAuthorToken. |
| people | ChatAuthor[] | no | Directory for the people picker, titles, and avatars. Display only. Identity stays user.id. If omitted, New still has a collapsed Other id field. |
| launcher | boolean | no | Corner bubble with unread total. Default off so full-width embeds stay unchanged. Same as presentation="launcher". Closed: button. Open: list + thread. Escape or backdrop closes. Position with --bsw-ch-launcher-right and --bsw-ch-launcher-bottom. |
| presentation | "section" | "launcher" | "window" | no | Default section (inline). launcher dims the page. window floats without a backdrop. Launcher and window start closed unless defaultOpen. Escape closes them. The backdrop closes launcher. |
| defaultOpen | boolean | no | Open launcher or window on first render. Ignored for section. |
| dir | "ltr" | "rtl" | no | Text direction. |
| theme | "light" | "dark" | no | Omit for the light cream default. No data-theme and no system theme. |
| className | string | no | Wrapper class for layout. |
Theme
theme is "light" | "dark". Omit it and the inbox stays the light cream default: the root has no data-theme attribute. theme="light" sets data-theme="light" and keeps those light tokens. theme="dark" sets data-theme="dark" and applies the dark shell. v1 does not follow prefers-color-scheme or the system theme.
Hosts may set these CSS custom properties on the embed root (.bsw-ch). These overrides are the supported API. Do not fight .bsw-* classes. When theme="dark" is set, target .bsw-ch[data-theme="dark"] so your values win over the dark defaults. Launcher offsets --bsw-ch-launcher-right and --bsw-ch-launcher-bottom stay layout, not skin.
- --bsw-ch-bg
- --bsw-ch-panel
- --bsw-ch-fg
- --bsw-ch-muted
- --bsw-ch-line
- --bsw-ch-accent
- --bsw-ch-accent-fg
- --bsw-ch-radius
.bsw-ch[data-theme="dark"] {
--bsw-ch-bg: #161714;
--bsw-ch-panel: #22241c;
--bsw-ch-fg: #f4f4ee;
--bsw-ch-muted: #b3b4aa;
--bsw-ch-line: #4a4c42;
--bsw-ch-accent: #c8ef00;
--bsw-ch-accent-fg: #161714;
--bsw-ch-radius: 12px;
}branding.primaryColor, when set, writes --bsw-ch-accent inline on the root and overrides the theme accent. className is for layout, not a second skin.
Resolve order
- If
adapteris passed, use it (BYO orcreateLocalAdapter). - Else if both
appIdandpublishableKeyare set, use Hosted. - Else fall back to
createLocalAdapter(demo / free).
Local / demo
import { Chat, createLocalAdapter } from "@bootstrapware/chat";
import "@bootstrapware/chat/styles.css";
<Chat
user={{ id: "user_1", name: "Ada" }}
people={[{ id: "user_kai", name: "Kai" }]}
adapter={createLocalAdapter({ storageKey: "demo-chat" })}
/>Launcher
Optional corner bubble. Marketing demos stay as the full inbox. Use this when you want a collapsed embed:
<Chat
launcher
user={currentUser}
people={workspaceMembers}
adapter={createLocalAdapter({ storageKey: "demo-chat" })}
/>Closed: a button with the unread total (sum of unreadCount). Open: the same list and thread. Escape or the backdrop closes. Nudge with CSS variables --bsw-ch-launcher-right and --bsw-ch-launcher-bottom. presentation="window" uses the same corner chrome without the dimming backdrop. launcher is the same as presentation="launcher". Launcher and window start closed unless defaultOpen.
Browser notifications
Optional and default off. Notify in the widget header is the only control that requests Notification permission; mounting the widget never prompts. After opt-in, a message.created from someone else can show a system notification while the tab is hidden. Visible-tab messages, the viewer's own messages, opted-out, and OS deny do not notify. Denied stays off with no re-prompt loop. Clicking a notification focuses the window and opens the thread. No email, SMS, or server push.
Markup
Messages are stored as the same 4,000-character string. The widget renders a tiny subset: **bold**, _italic_, inline `code`, and simple - / * lists, plus existing http(s) linkify. No raw HTML, images in markup, or headings. Hosted inbox and JSON export stay raw text.
Hosted
<Chat
appId="cha_..."
publishableKey="bsw_live_pub_..."
user={currentUser}
/>Live config fetch needs BYO ($9.99) or Hosted ($19.99). Live Hosted writes need Hosted ($19.99). Test publishable keys work unpaid.
Limits in the UI
Body max 4000 characters. Groups max 20. Poll fallback 4 seconds, or about 20 seconds when subscribe is healthy. Inbox and thread search misses use their own copy, not emptyState. See limits.
Related: Modes · Adapter · Identity · Quickstart · Live updates