Skip to content

BranderChatWidget

Floating chat widget with customizable trigger button

Import

import { BranderChatWidget, sseStream } from '@brander/sdk';

// Or with specific adapters
import { BranderChatWidget, anthropicStream, openaiStream, geminiStream } from '@brander/sdk';

Overview

BranderChatWidget is a floating chat interface that appears on top of your application. It includes:

  • Custom Trigger Button: You provide any React element as the trigger
  • Floating Popup: Widget appears above your content with an optional backdrop on desktop; on phones it fills the screen
  • Flexible Positioning: Choose from 4 corner positions
  • Smooth Animations: Configurable fade and slide effects
  • Mobile Responsive: Optimized for all screen sizes

Basic Usage (Streaming)

App.tsx
import { BranderChatWidget, sseStream } from '@brander/sdk';

function App() {
  return (
    <div className="app">
      <h1>My Application</h1>
      <p>Your main content here...</p>

      <BranderChatWidget
        apiKey="bux_pk_your_key"
        projectId="my-project-id"
        onQueryStream={(params) => sseStream("/api/agent", { params })}
        position="bottom-right"
      >
        {/* Custom trigger button */}
        <button style={{
          padding: '12px 24px',
          borderRadius: '50px',
          backgroundColor: '#0066FF',
          color: 'white',
          border: 'none',
          cursor: 'pointer'
        }}>
          Chat with AI
        </button>
      </BranderChatWidget>
    </div>
  );
}

Props

Required Props
  • apiKey: string - Your BranderUX project API key
  • projectId: string - Your BranderUX project ID
  • AI Handler (ONE of the following):
    • onQueryStream: StreamingCallback - Streaming handler (Recommended)
    • onQuery: OnQueryCallback - Non-streaming handler (Alternative)
  • children: React.ReactElement - Custom trigger button/element
Widget Configuration
  • position?: "bottom-right" | "bottom-left" | "top-right" | "top-left"
    Widget position on screen (default: "bottom-right")
  • offset?: { top?: number; bottom?: number; left?: number; right?: number }
    Offset from edges in pixels (default: { bottom: 24, right: 24 })
  • widgetSize?: { width?: string; height?: string }
    Widget popup dimensions (default: { width: "400px", height: "650px" })
Behavior
  • defaultOpen?: boolean - Initially open state (default: false)
  • open?: boolean - Open or closed, decided by your page (a controlled widget); leave it out and the widget keeps its own state
  • onOpenChange?: (open: boolean) => void - Every open or close the visitor asks for; with open set, update it from here
  • onOpen?: () => void - Callback when widget opens
  • onClose?: () => void - Callback when widget closes
  • showBackdrop?: boolean - Dim the page behind the popup on desktop; phones show the panel full screen with no backdrop (default: true)
  • backdropOpacity?: number - Backdrop opacity 0-1 (default: 0.2)
  • closeOnBackdropClick?: boolean - Close when clicking backdrop (default: true)
  • While open, the widget sets the brander-widget-open class on <html>, so a page with heavy animations can pause them behind the panel with one CSS rule
  • On phones the open panel follows the visual viewport, so the on-screen keyboard never covers the composer
  • The Back button closes the open panel; closing it any other way takes back the history entry it added, so Back then works as before
Styling
  • zIndex?: number - z-index for widget elements (default: 9999)
  • widgetClassName?: string - CSS class for popup container
  • panelBackground?: string - The panel's own colour, shown while your agent loads and behind it; set your page colour on a dark site. On phones the panel's close bar and the page behind the open panel take it too, so Android's navigation bar matches (default: #ffffff, transparent on phones)
  • loaderColor?: string - The loading spinner's colour (default: #3B82F6)
  • animationDuration?: number - Animation duration in ms (default: 300)
AI Configuration
  • variant?: "hybrid" | "classic" | "chat" - Display variant (default: "chat")
    • "hybrid": Full playground with scroll-snapping and query badges
    • "classic": Clean site-focused view with conversation sidebar
    • "chat": Chat-style scrolling with inline messages
Conversation History
  • conversations?: Conversation[] - Initial conversations to load
  • activeConversationId?: string - Currently active conversation ID
  • onConversationsChange?: (state: ConversationsState) => void - Callback for conversation state changes
Conversions (hosted mode)

With the hosted agent, the widget tells your page about every record the agent collects: a lead, a booking, an order or a request. onConversion receives the type, the entity and the record id, never personal data, and a brander:conversion DOM event fires on window either way. onConversationStart and a brander:conversation-start event mark the start of each conversation (the visitor's first action: a designed page opened or a question). Set trackConversions to forward both to the gtag, fbq, dataLayer and oaiq (ChatGPT Ads) already on your page. The widget also stamps where the visitor came from (campaign tags, click ids, the referring site and the landing page) on the records it collects; pass visitSource={false} to send nothing. It reads that prop once, when the widget mounts, and ignores later changes, so decide before mounting. By default it keeps the first capture in this tab's session storage as brander:visit-source (not a cookie; list it wherever your site lists what it stores); false neither stores nor sends anything.

<BranderChatWidget
  apiKey="bux_pk_your_key"
  projectId="your_project_id"
  onConversion={(event) => myAnalytics.track("lead", { type: event.type })}
>
  <button>Chat with us</button>
</BranderChatWidget>

Positioning Examples

Bottom Right (Default)
<BranderChatWidget
  {...props}
  onQueryStream={(params) => sseStream("/api/agent", { params })}
  position="bottom-right"
  offset={{ bottom: 24, right: 24 }}
>
  <button>Chat</button>
</BranderChatWidget>
Top Left
<BranderChatWidget
  {...props}
  onQueryStream={(params) => sseStream("/api/agent", { params })}
  position="top-left"
  offset={{ top: 80, left: 24 }}
>
  <button>Support</button>
</BranderChatWidget>
Bottom Left with Custom Size
<BranderChatWidget
  {...props}
  onQueryStream={(params) => sseStream("/api/agent", { params })}
  position="bottom-left"
  offset={{ bottom: 20, left: 20 }}
  widgetSize={{ width: "500px", height: "800px" }}
>
  <button>Help</button>
</BranderChatWidget>

Full Configuration Example

App.tsx
import { BranderChatWidget, sseStream } from '@brander/sdk';
import { MessageCircle } from 'lucide-react';

function App() {
  return (
    <div className="app">
      <header>
        <h1>My SaaS Application</h1>
      </header>

      <main>
        {/* Your main content */}
      </main>

      <BranderChatWidget
        // Required
        apiKey="bux_pk_your_key"
        projectId="my-project-id"
        onQueryStream={(params) => sseStream("/api/agent", { params })}
        // Widget Configuration
        position="bottom-right"
        offset={{ bottom: 24, right: 24 }}
        widgetSize={{ width: "450px", height: "700px" }}
        // Behavior
        defaultOpen={false}
        onOpen={() => console.log('Chat opened')}
        onClose={() => console.log('Chat closed')}
        showBackdrop={true}
        backdropOpacity={0.3}
        closeOnBackdropClick={true}
        // Styling
        zIndex={9999}
        animationDuration={250}
        widgetClassName="custom-chat-widget"
      >
        {/* Custom trigger button with icon */}
        <button
          style={{
            display: 'flex',
            alignItems: 'center',
            gap: '8px',
            padding: '14px 28px',
            backgroundColor: '#0066FF',
            color: 'white',
            border: 'none',
            borderRadius: '50px',
            fontSize: '16px',
            fontWeight: 600,
            cursor: 'pointer',
            boxShadow: '0 4px 12px rgba(0, 102, 255, 0.3)',
            transition: 'all 0.2s ease'
          }}
          onMouseEnter={(e) => {
            e.currentTarget.style.transform = 'scale(1.05)';
            e.currentTarget.style.boxShadow = '0 6px 16px rgba(0, 102, 255, 0.4)';
          }}
          onMouseLeave={(e) => {
            e.currentTarget.style.transform = 'scale(1)';
            e.currentTarget.style.boxShadow = '0 4px 12px rgba(0, 102, 255, 0.3)';
          }}
        >
          <MessageCircle size={20} />
          Chat with AI
        </button>
      </BranderChatWidget>
    </div>
  );
}

Alternative: Non-Streaming

App.tsx
import { BranderChatWidget } from '@brander/sdk';

function App() {
  return (
    <BranderChatWidget
      apiKey="bux_pk_your_key"
      projectId="my-project-id"
      onQuery={async (params) => {
        const response = await fetch('/api/ai', {
          method: 'POST',
          body: JSON.stringify(params)
        });
        return response.json();
      }}
      position="bottom-right"
    >
      <button>Chat</button>
    </BranderChatWidget>
  );
}

Custom Trigger Button Examples

Simple Button
<BranderChatWidget {...props}>
  <button className="chat-trigger">
    Chat
  </button>
</BranderChatWidget>
Floating Action Button (FAB)
<BranderChatWidget {...props}>
  <button style={{
    width: '60px',
    height: '60px',
    borderRadius: '50%',
    backgroundColor: '#1976d2',
    color: 'white',
    border: 'none',
    fontSize: '24px',
    cursor: 'pointer',
    boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)'
  }}>
    💬
  </button>
</BranderChatWidget>
With MUI Components
import { Fab } from '@mui/material';
import ChatIcon from '@mui/icons-material/Chat';

<BranderChatWidget {...props}>
  <Fab color="primary" aria-label="chat">
    <ChatIcon />
  </Fab>
</BranderChatWidget>
Custom Component
const CustomTrigger = () => (
  <div style={{
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    padding: '12px 20px',
    background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
    color: 'white',
    borderRadius: '12px',
    cursor: 'pointer',
    boxShadow: '0 4px 15px rgba(102, 126, 234, 0.4)'
  }}>
    <span>🤖</span>
    <span style={{ fontWeight: 600 }}>AI Assistant</span>
  </div>
);

<BranderChatWidget {...props}>
  <CustomTrigger />
</BranderChatWidget>

Use Cases

  • Customer Support: Add AI-powered chat support to any page
  • Help Centers: Provide instant answers without leaving the page
  • Sales Assistants: Guide users through product discovery
  • Documentation Helper: Answer questions about your product
  • Virtual Assistants: Provide personalized user assistance

BranderChatWidget vs Brander

FeatureBranderChatWidgetBrander
IntegrationFloating widgetInline embed
Trigger ButtonCustom (required)None
PositionConfigurable (4 corners)Fixed in layout
BackdropDesktop (optional)No
Best ForSupport, help centersDashboards, data tools
MobileOptimized overlayResponsive layout