Skip to content

Props Reference

Complete reference for all Brander component props

Required Props

apiKey
Required
string

Your BranderUX project API key. Must start with bux_pk_. Generate it in your BranderUX dashboard at Projects → API Keys. At runtime the SDK exchanges it for a short-lived token, the raw key never rides the iframe URL.

<Brander apiKey="bux_pk_your_key" projectId="..." onQueryStream={...} />
betaKey
Deprecated
string

Deprecated, use apiKey instead. Legacy bux_dp_ design-partner keys are still accepted during the migration window.

projectId
Required
string

Your BranderUX project ID. Get this from your BranderUX dashboard. Each project has its own configuration, branding, and data sources.

<Brander apiKey="..." projectId="your_project_id" onQueryStream={...} />
AI Handler (optional since 0.6.0)
onQueryStream
Recommended
StreamingCallback

Streaming AI handler using AG-UI events. Provides progressive UI loading for the best user experience. Use with stream adapters: sseStream, anthropicStream, openaiStream, geminiStream.

Function Signature:
type StreamingCallback = (
  params: CustomerAIParams
) => AsyncIterable<AGUIEvent> | ReadableStream<AGUIEvent>;
Usage with sseStream (Most Common):
import Brander, { sseStream } from "@brander/sdk";

<Brander
  apiKey="bux_pk_your_key"
  projectId="your_project_id"
  onQueryStream={(params) => sseStream("/api/agent", { params })}
/>
Usage with Provider Adapters:
import Brander, { anthropicStream } from "@brander/sdk";
import Anthropic from "@anthropic-ai/sdk";

const anthropic = new Anthropic({ apiKey: "..." });

<Brander
  apiKey="bux_pk_your_key"
  projectId="your_project_id"
  onQueryStream={async function*(params) {
    const stream = anthropic.messages.stream({
      model: "claude-sonnet-5",
      // Your persona + BranderUX UI instructions, append, never replace
      system: YOUR_SYSTEM_PROMPT + "\n\n" + params.system,
      messages: params.messages,
      tools: params.tools?.anthropic,
    });
    yield* anthropicStream(stream);
  }}
/>
onQuery
Alternative
OnQueryCallback

Non-streaming AI handler. Returns complete response at once. Use for simple integrations that don't need streaming.

Function Signature:
type OnQueryCallback = (
  params: CustomerAIParams
) => Promise<CustomerAIResponse>;
Usage:
import Brander from "@brander/sdk";

<Brander
  apiKey="bux_pk_your_key"
  projectId="your_project_id"
  onQuery={async (params) => {
    const response = await fetch("/api/ai", {
      method: "POST",
      body: JSON.stringify(params),
    });
    return response.json();
  }}
/>
CustomerAIParams (passed to your handler)
interface CustomerAIParams {
  system?: string;             // System instructions (A2UI protocol in flexible mode)
  messages: Array<{
    role: "user" | "assistant";
    content: string;
  }>;
  tools?: MultiProviderTools;  // Screen tools in all provider formats
  max_tokens?: number;         // Suggested max tokens
}

// Tools provided in all provider formats
interface MultiProviderTools {
  anthropic: AnthropicTool[];  // Use with Anthropic Claude
  openai: OpenAITool[];        // Use with OpenAI GPT
  gemini: GeminiTool[];        // Use with Google Gemini
}

Optional Props

PropTypeDefaultDescription
variant"hybrid" | "classic" | "chat""chat"Display variant: hybrid (full playground), classic (site-focused), chat (inline messages)
defaultSidebarOpenbooleantrueDefault state of conversation sidebar in classic variant
languagestring"en"BCP-47 tag for the widget's own copy and text direction ("he", "ar", "pt-BR"). RTL languages flip the layout automatically; English and Hebrew are built in, other languages are translated on first use
titlestring"Brander Widget"Accessible name of the widget's iframe — what a screen reader announces when the visitor reaches the frame. Name it after the business, and give each widget its own name when a page carries more than one
conversationsConversation[]undefinedInitial conversations to load for persistence
activeConversationIdstringundefinedID of currently active conversation
onConversationsChangefunctionundefinedCallback when conversation state changes
actionHandlersRecord<string, ElementActionHandler>undefinedDeterministic actions: keyed by the exact element action name (e.g. onAddToCart), or scoped to one element as "custom:<key>.onAddToCart". A registered action runs your handler in your app (your session and API clients) instead of becoming an AI query; return { followUpQuery } to run a query afterwards. Handler errors are logged, never retried. Find the names in the Element Library (Interactions → Copy actionHandlers) or via the MCP list_elements contract.
widthstring"100%"Widget container width
heightstring"600px"Widget container height
classNamestringundefinedCSS class for container
styleCSSPropertiesundefinedInline styles for container

Conversation Persistence

Use the conversation props to persist chat history across sessions:

import Brander, { sseStream } from "@brander/sdk";
import { useState, useEffect } from "react";

function App() {
  const [conversations, setConversations] = useState([]);
  const [activeId, setActiveId] = useState(null);

  // Load from storage on mount
  useEffect(() => {
    const saved = localStorage.getItem("brander_conversations");
    if (saved) {
      const state = JSON.parse(saved);
      setConversations(state.conversations);
      setActiveId(state.activeConversationId);
    }
  }, []);

  return (
    <Brander
      apiKey="bux_pk_your_key"
      projectId="your_project_id"
      onQueryStream={(params) => sseStream("/api/agent", { params })}
      conversations={conversations}
      activeConversationId={activeId}
      onConversationsChange={(state) => {
        setConversations(state.conversations);
        setActiveId(state.activeConversationId);
        localStorage.setItem("brander_conversations", JSON.stringify(state));
      }}
    />
  );
}

Deterministic Actions (actionHandlers)

By default, every click in a generated screen becomes the next AI query. For actions that should call your API instead, add to cart, place order, subscribe, register a handler. It runs in your page (your session, your API clients), and the click never becomes a query. Unregistered actions keep the conversational behavior.

<Brander
  apiKey="bux_pk_your_key"
  projectId="your_project_id"
  onQueryStream={(params) => sseStream("/api/agent", { params })}
  actionHandlers={{
    onPlaceOrder: async ({ item }) => {
      await myApi.orders.create(item);          // your backend, your session
      return { followUpQuery: "Show my order confirmation" }; // optional
    },
    onAddToCart: async ({ item }) => {
      await myApi.cart.add(item.id);            // pure side-effect: no return
    },
    // Navigation actions (onSelectProduct, onView…) stay UNregistered so
    // browsing remains conversational.
  }}
/>
Where the keys come from

Keys are the element's declared action names, verbatim, you never invent them:

  • On the dashboard: Projects → Element Library → select an element → Copy actionHandlers, a ready-to-paste map with the exact keys (bodies as TODOs). Each element's publish review also lists its actions by name.
  • Via the MCP: list_elements returns actions[] per element, name, meaning, item shape and a concrete example item, so your coding agent can generate the map in one call.
  • Typo safety: at runtime the SDK warns in your console when a registered key matches no element action.
Same action name on several elements

A bare key is a catch-all: registering onSelect fires that handler for every element declaring an onSelect, the payload's elementKey tells you which one fired. When two elements share a name with different meanings, scope the key to one element instead:

actionHandlers={{
  // Only the product grid's onSelect, other elements' onSelect stays conversational
  "custom:product-grid.onSelect": async ({ item }) => myApi.cart.add(item.id),
  // Only the order list's onSelect
  "custom:order-list.onSelect": async ({ item }) => openOrderPanel(item.id),
  // Bare keys still work everywhere as before
  onAddToCart: async ({ item }) => myApi.cart.add(item.id),
}}

A scoped match wins over the bare name, and scoping is the safe way to handle one element's select/click without hijacking navigation on the rest. The exact scoped key ships as actions[].scopedKey in the MCP list_elements contract, and the dashboard's Copy actionHandlers emits scoped keys automatically whenever names collide.

Complete Example

App.tsx
import Brander, { sseStream } from "@brander/sdk";

function App() {
  return (
    <Brander
      apiKey="bux_pk_your_key"
      projectId="my-project-id"
      onQueryStream={(params) => sseStream("/api/agent", { params })}
      variant="classic"
      width="100%"
      height="700px"
    />
  );
}