Theming & Slots

Retheme the widget with a typed prop or plain CSS, switch to dark mode, and replace individual pieces of chrome without forking the component.

The theme prop

The friendliest option for React hosts — pass a typed object and the widget maps it to the equivalent --cf-* CSS variables on its root element.

tsx
<ChatterflyWidget
  {...props}
  theme={{
    primary: "#0ea5e9",
    radius: "4px",
    font: '"Inter", system-ui, sans-serif',
  }}
/>

CSS variables

Override any of these on an ancestor element. Every Theming v2 token (bubbles, header, input background, shadow, spacing, avatar size) falls back to a primitive above it, so you only need to set what you want to change.

Variabletheme keyDefault
--cf-primaryprimary — #5B4AE4
--cf-accentaccent — #F28B30
--cf-infoinfo — #3A8FD6
--cf-bgbg — #ffffff
--cf-fgfg — #0C1B3A
--cf-mutedmuted — #1E3A5E
--cf-borderborder — #E8EDF6
--cf-surfacesurface — #F4F6FC
--cf-errorerror — #E04848
--cf-radiusradius — 10px
--cf-fontfont — IBM Plex Sans
--cf-heightheight — 480px (inline mode)
--cf-bubble-bot-bg / --cf-bubble-bot-fgbubbleBotBg / bubbleBotFg — var(--cf-surface) / var(--cf-fg)
--cf-bubble-user-bg / --cf-bubble-user-fgbubbleUserBg / bubbleUserFg — var(--cf-primary) / #ffffff
--cf-header-bg / --cf-header-fgheaderBg / headerFg — reserved for the header slot
--cf-input-bginputBg — var(--cf-bg)
--cf-shadowshadow — none
--cf-spacingspacing — 1rem
--cf-avatar-sizeavatarSize — 28px (reserved)
css
.my-widget-host {
  --cf-primary: #0ea5e9;
  --cf-radius: 4px;
  --cf-font: "Inter", system-ui, sans-serif;
}

Dark mode

tsx
<ChatterflyWidget {...props} colorScheme="dark" />  {/* explicit preset */}
<ChatterflyWidget {...props} colorScheme="auto" />  {/* follows prefers-color-scheme */}
Tip: colorSchemeships a complete dark token set — you don't need to override every variable yourself to support dark mode.

Chrome slots

For layout changes beyond CSS variables, render your own content around the conversation:

tsx
<ChatterflyWidget
  {...props}
  header={<MyBrandHeader />}
  footer={<MyPoweredByFooter />}
  emptyState={<MySkeleton />}
  completedState={<MyThankYouCard />}
  errorState={<MyErrorCard />}
/>

header/footerrender on the conversational states (main render, replaying, deliberation chat, voice input) — not on the distinct full-panel form/editor layouts, where a chat header isn't a natural fit.

Bubble overrides

tsx
<ChatterflyWidget
  {...props}
  components={{
    BotBubble: ({ children }) => <div className="my-bot-bubble">{children}</div>,
    UserBubble: ({ children }) => <div className="my-user-bubble">{children}</div>,
  }}
/>
Note: Overrides apply to the plain-text chat bubbles only — form-submission summaries and document cards keep their default rendering.