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

PropTypeRequiredNotes
userChatAuthor | nullyesOpaque stable id. Optional name, email, avatarUrl for display.
adapterChatAdapterBYO / localIf set, wins over Hosted. Use createLocalAdapter or your own API.
appIdstringHosted / BYO configWith publishableKey for Hosted. Prefix cha_.
publishableKeystringHostedBrowser-safe. Never put secret keys in the client.
apiBaseUrlstringnoDefaults to https://chat.bootstrapware.co
configPartial<ChatAppConfig>noOverrides toggles / empty state until Hosted config loads.
authorTokenstringnoShort-lived host-signed assertion from POST /api/v1/author-tokens (secret key). Required when the app sets requireAuthorToken.
peopleChatAuthor[]noDirectory for the people picker, titles, and avatars. Display only. Identity stays user.id. If omitted, New still has a collapsed Other id field.
launcherbooleannoCorner bubble with unread total. Default off so full-width embeds stay unchanged. Closed: button. Open: list + thread. Escape or backdrop closes. Position with --bsw-ch-launcher-right and --bsw-ch-launcher-bottom.
defaultOpenbooleannoInitial open state when launcher is true.
dir"ltr" | "rtl"noText direction.
classNamestringnoWrapper class for layout.

Resolve order

  1. If adapter is passed, use it (BYO or createLocalAdapter).
  2. Else if both appId and publishableKey are set, use Hosted.
  3. 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.

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. See limits.

Related: Modes · Adapter · Identity · Quickstart · Live updates