The headless path skips the default UI, but the widget options behave the same. Use the Playground to validate option shapes live before wiring them into your headless setup.
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
Step 2: Wire It Into The Headless Widget
Start withWidgetProvider, then render your own chat UI with the hooks and components you need.
App.tsx
WidgetProvider. The Hooks Reference documents every input, return field, and provider requirement.
Providers Reference
The headless package ships two providers.WidgetProvider is required; WidgetTriggerProvider is only needed when a launcher button lives outside the widget surface (e.g. in your site header).
WidgetProvider
Initializes the widget engine and powers every hook below it in the tree.
Props
WidgetTriggerProvider
Scopes useWidgetTrigger so a trigger button can live outside the widget surface while still toggling the widget open.
Props
Hooks Reference
Every hook must be called inside<WidgetProvider>, with one exception: useWidgetTrigger requires <WidgetTriggerProvider>.
Conversation
useMessages()
Read the message stream and send new user messages.
useSessions()
Access session state, switch sessions, resolve the current session, and persist state checkpoints.
useCsat()
Drive the CSAT survey flow — detect when the server requested feedback and submit a score.
useIsAwaitingBotReply()
Lightweight flag for “is the bot replying?” — use this for typing indicators without subscribing to the full message list.
Widget state
useWidget()
Access the low-level widget context. Prefer the higher-level hooks unless you need raw context, the component registry, or the engine version.
useConfig()
Shortcut to the live WidgetConfig. Equivalent to useWidget().config.
useContact()
Read the current contact (verified or unverified) and create an unverified contact on the fly when you collect identity inside your own UI.
useDocumentDir()
Resolved text direction — 'ltr' or 'rtl' — so your custom UI can mirror it.
Navigation & trigger
useWidgetRouter()
Navigate between the built-in screens (sessions list ↔ chat) from your custom UI.
useWidgetTrigger()
Read and toggle the widget’s open/closed state from anywhere inside <WidgetTriggerProvider>.
Requires: Inside <WidgetTriggerProvider>.
Modes & files
useModes()
Read the configured modes, the currently active mode, and the component that renders it.
useUploadFiles()
Manage attachment uploads — queue, progress, cancel, retrieve URLs — to pair with useMessages().sendMessage.
Utilities
usePrimitiveState<T>()
Subscribe a React component to a PrimitiveState<T> published by the widget core. Returns the current value and re-renders on change.
@opencx/widget-core. Most app code should prefer the higher-level hooks above.
Types Reference
WidgetComponentProps<TData = unknown>
Prop type passed to custom renderers. It is a discriminated union of the three message kinds — switch on the message shape (or data.action?.name for AI action results) to decide what to render.
WidgetComponentType
Registration entry for the components prop of WidgetProvider.
FileWithProgress
Queue entry returned by useUploadFiles.
Step 3: Add Storage When The Browser Default Is Not Enough
If browser storage is not the right fit, pass an toWidgetProvider.
Signature
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
Own the fallback UI
Own the fallback UI
Decide what visitors should see when no custom action renderer matches, when a payload is incomplete, or when a session is resolved.
Keep action names stable
Keep action names stable
Your renderer depends on action names and payload shapes staying predictable.
Test history and identity
Test history and identity
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.
Related Documentation
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.