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)
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 keyprojectId: 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 stateonOpenChange?: (open: boolean) => void- Every open or close the visitor asks for; withopenset, update it from hereonOpen?: () => void- Callback when widget opensonClose?: () => void- Callback when widget closesshowBackdrop?: 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-openclass 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 containerpanelBackground?: 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 loadactiveConversationId?: string- Currently active conversation IDonConversationsChange?: (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
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
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
| Feature | BranderChatWidget | Brander |
|---|---|---|
| Integration | Floating widget | Inline embed |
| Trigger Button | Custom (required) | None |
| Position | Configurable (4 corners) | Fixed in layout |
| Backdrop | Desktop (optional) | No |
| Best For | Support, help centers | Dashboards, data tools |
| Mobile | Optimized overlay | Responsive layout |