React component

<Comments /> is a client component. Pass the signed-in person as user and the record as scope. There is no viewerId prop. The adapter's viewerId argument is filled from user.id.

Props

  • user: { id, name?, avatarUrl? }. id max 200. name max 120. Names are cosmetic. The id is the actor. There is no email field.
  • scope: { tenantKey, resourceType, resourceId }. Each field max 200. A single-tenant app still sends a stable tenantKey.
  • appId: published cma_ id. Local mode uses cma_local when omitted.
  • publishableKey: bsw_test_pub_… or bsw_live_pub_…. Together with appId and no adapter, this selects Hosted.
  • apiBaseUrl: defaults to https://comments.bootstrapware.co.
  • adapter: a CommentsAdapter. Wins over Hosted and local.
  • authorToken: bsw_cmauth_v1. token from your server. The widget never mints one.
  • renewAuthorToken: () => Promise<string>. Called when unverified exp is under 60 seconds away, and again on an expired token, at most once every 30 seconds.
  • permissions: button visibility when no token is present. A token's perms replace this list. It is not a grant. The local adapter still refuses moderate without a token.
  • searchMentionCandidates and resolveUsers: host callbacks in every mode.
  • layout: "threads" (default), "section", or "forum". Threads is a discussion list with flat replies (parentCommentId stays null). Section is one chronological comment stream under the record, with nested replies and no thread list. Forum is a topic list; each topic is a nested reply tree, and resolve still closes that topic. Nested replies indent through depth 5. Depth 6 is rejected. If every section thread is resolved, a viewer with create can still post a top-level comment; that starts a new implicit thread.
  • theme: "light" or "dark". Omit it and the shell stays the light cream default. v1 does not follow prefers-color-scheme.
  • presentation: "section" (default, inline), "modal", "window", or "launcher". Separate from layout. Modal, window, and launcher start closed unless defaultOpen. Escape closes them. The backdrop closes modal and launcher. Nudge the corner with --bsw-cm-frame-launcher-right and --bsw-cm-frame-launcher-bottom.
  • defaultOpen: open modal, window, or launcher on first render. Ignored for section.
  • onUnreadChange(count): fires when the unread total for the current view changes, after that view has loaded. Threads and forum sum unreadCount on the threads loaded for the active filter. Section sums the section's threads. The same callback runs for Hosted and BYO.
  • Comment bodies stay plain text in storage. The widget renders a small inline subset: **bold**, _italic_ or *italic* next to a letter or digit, `code`, [label](https://…), and bare http / https URLs. Links use rel="nofollow noopener" and target="_blank". Raw HTML and other schemes stay text. Mentions win when markdown would cross them.
  • pageSize: default 50, maximum 100. dir is ltr or rtl. className is layout only.

Resolve order

Explicit adapter, then Hosted when both appId and publishableKey are set, otherwise a memory-only local adapter. Pass createLocalAdapter({ storageKey }) when you want localStorage.

Switching user, tenant, or resource cancels in-flight requests, drops caches, closes the stream, and keeps a separate composer draft. A response for a previous tuple is ignored.

Theme tokens on .bsw-cm, or .bsw-cm[data-theme="dark"] when theme="dark": --bsw-cm-bg, --bsw-cm-panel, --bsw-cm-fg, --bsw-cm-muted, --bsw-cm-line, --bsw-cm-accent, --bsw-cm-accent-fg, --bsw-cm-radius. Published branding.primaryColor sets --bsw-cm-accent.

Full install paths: React guide.

Related: Adapter · Host themes · Modes