Skip to main content
The React headless package provides hooks and context providers for building custom widget UIs. It wraps @opencx/widget-core with React primitives.

Installation

npm install @opencx/widget-react-headless

WidgetProvider

The root provider that initializes the widget context. All hooks must be used within this provider.
import { WidgetProvider } from '@opencx/widget-react-headless';

function App() {
  return (
    <WidgetProvider
      options={{ token: '<YOUR_TOKEN>' }}
      loadingComponent={<div>Loading...</div>}
    >
      <YourCustomWidget />
    </WidgetProvider>
  );
}

Props

PropTypeRequiredDescription
optionsWidgetConfigYesWidget configuration object
childrenReactNodeYesChild components
componentsWidgetComponentType[]NoCustom component overrides
storageExternalStorageNoCustom storage implementation
loadingComponentReactNodeNoComponent shown while initializing

Hooks

useWidget

Access the raw widget context for advanced use cases.
import { useWidget } from '@opencx/widget-react-headless';

function Component() {
  const { widgetCtx, config, version } = useWidget();
}
PropertyTypeDescription
widgetCtxWidgetCtxCore widget context
configWidgetConfigProvider configuration
versionstringPackage version

useConfig

Access the widget configuration.
import { useConfig } from '@opencx/widget-react-headless';

function Component() {
  const config = useConfig();
}

useMessages

Manage chat messages for the current session.
import { useMessages } from '@opencx/widget-react-headless';

function Chat() {
  const { messagesState, sendMessage } = useMessages();
  const [input, setInput] = useState('');

  const handleSend = () => {
    sendMessage({ content: input });
    setInput('');
  };

  return (
    <div>
      {messagesState.messages.map((msg) => (
        <Message key={msg.id} message={msg} />
      ))}
      <input value={input} onChange={(e) => setInput(e.target.value)} />
      <button onClick={handleSend} disabled={messagesState.isSendingMessage}>
        Send
      </button>
    </div>
  );
}
PropertyTypeDescription
messagesState.messagesWidgetMessageU[]Array of messages
messagesState.isSendingMessagebooleanMessage send in progress
messagesState.isSendingMessageToAIbooleanWaiting for AI response
sendMessage(input: { content: string; attachments?; customData? }) => Promise<void>Send a message

useSessions

Manage chat sessions.
import { useSessions } from '@opencx/widget-react-headless';

function SessionsList() {
  const {
    sessionState,
    sessionsState,
    openSessions,
    closedSessions,
    canCreateNewSession,
    loadMoreSessions,
    resolveSession,
  } = useSessions();
}
PropertyTypeDescription
sessionState.sessionSessionDto | nullActive session
sessionState.isCreatingSessionbooleanSession creation in progress
sessionsState.dataSessionDto[]All sessions
openSessionsSessionDto[]Filtered open sessions
closedSessionsSessionDto[]Filtered closed sessions
canCreateNewSessionbooleanCan create new session
loadMoreSessions() => Promise<void>Load next page of sessions
resolveSession() => Promise<{ success: boolean }>Resolve current session
createStateCheckpoint(payload: unknown) => Promise<{ success: boolean }>Create checkpoint
See SessionCtx for state details.

useCsat

Handle CSAT prompts and submissions.
import { useCsat } from '@opencx/widget-react-headless';

function Example() {
  const {
    isCsatRequested,
    isCsatSubmitted,
    submitCsat,
    submittedScore,
    submittedFeedback,
  } = useCsat();

  if (isCsatSubmitted) {
    return <div>Thanks! Score: {submittedScore}</div>;
  }

  if (isCsatRequested) {
    return (
      <div>
        {[1, 2, 3, 4, 5].map((score) => (
          <button key={score} onClick={() => submitCsat({ score })}>
            {score}
          </button>
        ))}
      </div>
    );
  }

  return null;
}
PropertyTypeDescription
isCsatRequestedbooleanCSAT prompt active
isCsatSubmittedbooleanCSAT already submitted
csatRequestedMessageWidgetSystemMessage__CsatRequestedRequest message
csatSubmittedMessageWidgetSystemMessage__CsatSubmittedSubmission message
submittedScorenumber | undefinedSubmitted score
submittedFeedbackstring | undefinedSubmitted feedback
submitCsat(opts: { score: number; feedback?: string }) => voidSubmit rating

useIsAwaitingBotReply

Determine if waiting for a bot response.
import { useIsAwaitingBotReply } from '@opencx/widget-react-headless';

function TypingIndicator() {
  const { isAwaitingBotReply } = useIsAwaitingBotReply();

  if (!isAwaitingBotReply) return null;
  return <div>AI is typing...</div>;
}
PropertyTypeDescription
isAwaitingBotReplybooleanWaiting for bot response

useUploadFiles

Handle file uploads with progress tracking.
import { useUploadFiles } from '@opencx/widget-react-headless';

function FileUploader() {
  const { allFiles, appendFiles, handleCancelUpload, isUploading } = useUploadFiles();

  return (
    <div>
      <input
        type="file"
        multiple
        onChange={(e) => e.target.files && appendFiles(Array.from(e.target.files))}
      />
      {allFiles.map((f) => (
        <div key={f.id}>
          {f.file.name} - {f.progress}%
        </div>
      ))}
    </div>
  );
}
PropertyTypeDescription
allFilesFileWithProgress[]All tracked files
appendFiles(files: File[]) => voidAdd files to queue
handleCancelUpload(fileId: string) => voidCancel upload
successFilesFileWithProgress[]Completed uploads
emptyTheFiles() => voidClear all files
isUploadingbooleanUpload in progress
hasErrorsbooleanAny upload failed
FileWithProgress
interface FileWithProgress {
  id: string;
  file: File;
  status: 'pending' | 'uploading' | 'success' | 'error';
  progress: number;
  fileUrl?: string;
  error?: string;
}

useWidgetRouter

Navigate between widget screens.
import { useWidgetRouter } from '@opencx/widget-react-headless';

function Navigation() {
  const { routerState, toSessionsScreen, toChatScreen } = useWidgetRouter();

  return (
    <div>
      <button onClick={toSessionsScreen}>Sessions</button>
      <button onClick={() => toChatScreen('session-id')}>Chat</button>
    </div>
  );
}
PropertyTypeDescription
routerStateRouterStateCurrent router state
toSessionsScreen() => voidNavigate to sessions
toChatScreen(sessionId?: string) => voidNavigate to chat

useWidgetTrigger

Control widget open/close state. Requires WidgetTriggerProvider.
import { WidgetTriggerProvider, useWidgetTrigger } from '@opencx/widget-react-headless';

function WidgetLauncher() {
  const { isOpen, setIsOpen } = useWidgetTrigger();

  return <button onClick={() => setIsOpen(!isOpen)}>{isOpen ? 'Close' : 'Open'}</button>;
}

function App() {
  return (
    <WidgetProvider options={{ token: '<TOKEN>' }}>
      <WidgetTriggerProvider>
        <WidgetLauncher />
      </WidgetTriggerProvider>
    </WidgetProvider>
  );
}
PropertyTypeDescription
isOpenbooleanWidget open state
setIsOpenDispatch<SetStateAction<boolean>>Toggle state

useModes

Access widget conversation modes.
import { useModes } from '@opencx/widget-react-headless';

function ModeSelector() {
  const { modes, activeMode, Component } = useModes();

  return (
    <div>
      <p>Active: {activeMode?.name}</p>
      {Component && <Component />}
    </div>
  );
}
PropertyTypeDescription
modesMode[]Available modes
activeModeIdstring | undefinedActive mode ID
activeModeMode | undefinedActive mode object
ComponentComponentType | undefinedMode component

useContact

Access current contact information.
import { useContact } from '@opencx/widget-react-headless';

function ContactInfo() {
  const contact = useContact();

  return <div>Email: {contact?.email}</div>;
}

Additional Resources