Skip to main content
Use this page when the default widget UI is not the right fit and you want to own the rendering layer yourself.
Need the broader concept first? Start with Custom Components.

How Headless Component Rendering Works

For a headless implementation, you wire the widget engine into your own UI and decide which custom component to render for each action result. That usually means:
  • wrapping your app in
  • rendering your own message UI
  • switching on props.data.action?.name
  • owning fallback states and storage behavior

Step 1: Build The Message Component

Use so your renderer has access to the action name and payload.
AIAgentMessage.tsx
import { WidgetComponentProps } from '@opencx/widget-react-headless';
import { AccountBalanceCard } from './AccountBalanceCard';
import { SpendingSummaryCard } from './SpendingSummaryCard';

export function AIAgentMessage(props: WidgetComponentProps) {
  switch (props.data.action?.name) {
    case 'getAccountBalance':
      return <AccountBalanceCard {...props} />;
    case 'getSpendingSummary':
      return <SpendingSummaryCard {...props} />;
    default:
      return <div>{props.data.content}</div>;
  }
}

Step 2: Wire It Into The Headless Widget

Start with WidgetProvider, then render your own chat UI with the hooks and components you need.
App.tsx
import { WidgetProvider } from '@opencx/widget-react-headless';

export function App() {
  return (
    <WidgetProvider options={{ token: '<WIDGET_TOKEN>' }}>
      <CustomWidget />
    </WidgetProvider>
  );
}
From there, your custom widget can use hooks like:

Step 3: Add Storage When The Browser Default Is Not Enough

If browser storage is not the right fit, use .
const storage = {
  async get(key: string) {
    return SecureStore.getItemAsync(key);
  },
  async set(key: string, value: string) {
    await SecureStore.setItemAsync(key, value);
  },
  async remove(key: string) {
    await SecureStore.deleteItemAsync(key);
  },
};
This is especially useful in mobile apps, embedded environments, or custom shells where your app controls persistence itself.

Step 4: Use Modes When You Need A Guided Flow

Use when the widget should temporarily switch from normal chat to a guided flow such as onboarding, qualification, or structured data collection. Use an action-result component when you are displaying a result. Use a mode component when you are driving a flow.

What To Watch For

Decide what visitors should see when no custom action renderer matches, when a payload is incomplete, or when a session is resolved.
Your renderer depends on action names and payload shapes staying predictable.
Make sure your custom UI still handles returning visitors, resolved sessions, and verified identity correctly.

Good Headless Use Cases

Fully custom layouts

Build your own message thread, session list, composer, or embedded support layout.

Platform-specific storage

Use your own storage layer when browser defaults are not the right fit.

Action-aware rendering

Swap in different UI for different action results without relying on the default widget shell.

Guided flows

Combine headless rendering with modes when the widget needs to collect structured input over multiple steps.

Custom Components

Decide whether you need action-result UI or a full headless build.

React Components

Register custom UI on the default React widget.

Configuration

Pair headless builds with widget behavior, modes, and prompts.

Install Widget

Start with the right implementation path before building custom UI.