Skip to Content
    Qafka
    CTRL K
    CTRL K
    • Introduction
      • Quick Start (React Native)
      • Quick Start (Website)
      • React Native Configuration
      • Overview
      • npm & React
      • Options Reference
      • WordPress
      • React Native Widget
      • Headless SDK
      • Theming
      • Context
      • Navigation
      • External Navigation
      • Handling Tools
      • Voice Chat
      • Sub-Projects
      • Error Handling
      • CLI
      • Dashboard
      • Onboarding
      • Invitations
      • Billing
      • Usage
      • Settings
      • Sign-in & Account
      • Dashboard Assistant
        • Project
        • Overview
        • Conversations
        • Chat Test
        • Sub-Projects
        • Analysis
        • Unanswered Questions
        • Insights
        • Configuration
        • AI Behavior
        • Members
        • Documents
          • Overview
          • Create with AI
          • Versions
          • Response Channel
        • Action Logs
        • Navigation Rules
        • External Destinations
        • Chat Theme
        • PII Masking
        • Websites
        • Mobile Apps
        • API Keys
        • Project Settings
      • API Key Security
    • Introduction
      • Quick Start (React Native)
      • Quick Start (Website)
      • React Native Configuration
      • Overview
      • npm & React
      • Options Reference
      • WordPress
      • React Native Widget
      • Headless SDK
      • Theming
      • Context
      • Navigation
      • External Navigation
      • Handling Tools
      • Voice Chat
      • Sub-Projects
      • Error Handling
      • CLI
      • Dashboard
      • Onboarding
      • Invitations
      • Billing
      • Usage
      • Settings
      • Sign-in & Account
      • Dashboard Assistant
        • Project
        • Overview
        • Conversations
        • Chat Test
        • Sub-Projects
        • Analysis
        • Unanswered Questions
        • Insights
        • Configuration
        • AI Behavior
        • Members
        • Documents
          • Overview
          • Create with AI
          • Versions
          • Response Channel
        • Action Logs
        • Navigation Rules
        • External Destinations
        • Chat Theme
        • PII Masking
        • Websites
        • Mobile Apps
        • API Keys
        • Project Settings
      • API Key Security

    On This Page

    • Layouts
    • Imperative Handle
    • Customizing Components
    • Header Buttons (Back / Close)
    • Navigation Buttons
    • Voice Page Components
    • Tool Response Components
    • Callbacks by Scenario
    • Card CTAs
    • Actions with a built-in fallback
    • Actions that need a host callback
    • deep_link — internal route navigation
    • tool_trigger — fire another tool from a card
    • CTA telemetry
    Question? Give us feedback Edit this page 
    GuidesReact Native Widget

    React Native Widget

    The Qafka component is a drop-in chat widget with full UI, theming, streaming, and voice support.

    import { Qafka } from '@qafka/react-native' export default function ChatScreen() { return <Qafka projectId="proj_abc123" endUserId={currentUser?.id ?? 'anonymous'} /> }

    projectId and endUserId are required. For the full prop table (connection, appearance, behavior, callbacks) see Configuration. This page covers layout, the imperative handle, voice UI customization, and component slots.

    The widget calls useSafeAreaInsets internally, so make sure a <SafeAreaProvider> exists somewhere above it in the tree (Expo Router, React Navigation, and most RN starter templates already include one).

    Layouts

    <Qafka> always renders the same full chat surface — it fills whatever space its parent gives it. How that surface appears in your app depends on how you place it:

    • Full screen: mount <Qafka /> as its own screen/route. This is the common case.
    • Embedded / inline: wrap it in a sized container and pass style to constrain it, e.g. a fixed-height panel inside another screen.
    • Floating bubble: use QafkaProvider instead of Qafka directly — it renders a floating launcher button that opens/closes the chat surface.
    import { QafkaProvider } from '@qafka/react-native' export default function App() { return ( <QafkaProvider projectId="proj_abc123" endUserId={currentUser?.id ?? 'anonymous'} position="bottom-right" /> ) }

    QafkaProvider doesn’t take every Qafka prop — only a subset:

    • Connection/identity: projectId, apiUrl, isAuthenticated, endUserId, endUserData, context, contextDescription
    • Appearance: style, theme, themeOverride, customTheme, components
    • Behavior: enableStreaming, showTimestamps, placeholder, maxMessageLength, greetingMessage
    • Callbacks: onReady, onMessageSent, onResponseReceived, onError, onNavigationSuggest, onNavigationAction, onToolSuggested, onActionResult, onStepCompleted, onClose, onBack
    • Component slots: CloseComponent, BackComponent, navigationLabelFormat, NavigationButtonComponent

    It does not take devConfig, subProjectId, locale, onUnavailable, voice props (voiceEnabled, voiceTranscript, toolRenderMode, voiceComponents), onExternalSuggestion, the card-CTA callbacks (onCardDeepLink, onCardSuggestMessage, onCardExternalNavigation, onCardShare, onCardCopy, onCardToolTrigger, onCardCTAClick), the file/extraction/tool-data callbacks (onFileUploadRequest, onExtractionResult, onToolDataRequested), or the imperative ref (it manages open/closed state itself) — don’t assume full parity with <Qafka>. If your app needs any of those, mount <Qafka> directly instead.

    It also accepts:

    PropTypeDefaultDescription
    mode'floating' | 'fullscreen' | 'inline''floating''floating' renders the launcher button + collapsible panel. 'fullscreen'/'inline' render Qafka directly (no launcher), which is equivalent to just using <Qafka>.
    position'bottom-right' | 'bottom-left' | 'top-right' | 'top-left''bottom-right'Corner the floating launcher button anchors to. Only applies in 'floating' mode.

    mode, title, and showHeader on <Qafka> itself are not read by the component — see Configuration › Props with no effect. Use QafkaProvider for the floating layout instead.

    Imperative Handle

    Pass a ref to Qafka to control it programmatically:

    import { useRef } from 'react' import { Qafka, type QafkaHandle } from '@qafka/react-native' const qafkaRef = useRef<QafkaHandle>(null) <Qafka ref={qafkaRef} projectId="proj_abc123" endUserId="anonymous" /> // Send a message as if the user typed it qafkaRef.current?.sendMessage('Show my orders') // Voice session control await qafkaRef.current?.connectVoice() await qafkaRef.current?.disconnectVoice() await qafkaRef.current?.pauseMic() await qafkaRef.current?.resumeMic() // User-controlled mute — independent from the mic pause the SDK applies // internally during AI/tool transitions; persists across those like a // Teams/Zoom mute button qafkaRef.current?.toggleMute() qafkaRef.current?.mute() qafkaRef.current?.unmute() qafkaRef.current?.isMuted // boolean, reflects the user toggle only // Voice loading pill (for async work outside onToolSuggested) qafkaRef.current?.setLoading(true, 'Fetching data…') qafkaRef.current?.setLoading(false) // Reset voice tool cards qafkaRef.current?.clearRenderedTools()

    Customizing Components

    The widget renders sensible defaults for every interactive surface, but you can swap any of them out for your own component to match your app’s visual language.

    Header Buttons (Back / Close)

    The back and close buttons appear in the header only when you wire up their callbacks. To replace the default rendering, pass BackComponent / CloseComponent:

    <Qafka // projectId, endUserId, … onBack={() => navigation.goBack()} onClose={() => navigation.navigate('Home')} BackComponent={() => <MyBackButton />} CloseComponent={() => <MyCloseButton />} />

    Navigation Buttons

    When the AI suggests a navigation target, the widget renders a button. Replace the rendering with NavigationButtonComponent, or just rewrite the label with navigationLabelFormat.

    // Just change the label text <Qafka // projectId, endUserId, … navigationLabelFormat={(screen) => `Go to ${screen}`} /> // Replace the entire button <Qafka // projectId, endUserId, … NavigationButtonComponent={({ screenName, onPress, theme, label }) => ( <TouchableOpacity onPress={onPress} style={myButtonStyle(theme)}> <Text>{label}</Text> </TouchableOpacity> )} />

    The component receives { screenName, suggestion, onPress, theme, style, label, icon }.

    Voice Page Components

    The voice page exposes four slots — animation indicator, background container, transcript text, and the mute button. Provide any subset; missing ones fall back to defaults.

    import LottieView from 'lottie-react-native' import voiceLottie from './voice-blob.json' <Qafka // projectId, endUserId, … voiceComponents={{ VoiceIndicator: ({ state, amplitude, theme, isMuted }) => ( <LottieView source={voiceLottie} autoPlay loop speed={state === 'speaking' ? 1.5 : 1} /> ), VoiceBackground: ({ state, children, theme }) => ( <LinearGradient colors={['#1A1A2E', '#16213E']} style={{ flex: 1 }}> {children} </LinearGradient> ), VoiceTranscript: ({ transcript, userTranscript, state, theme }) => ( <Text style={{ color: theme.colors.text }}> {state === 'listening' ? userTranscript : transcript} </Text> ), VoiceMuteButton: ({ isMuted, onToggle, theme, state }) => ( <TouchableOpacity onPress={onToggle}> <Icon name={isMuted ? 'mic-off' : 'mic'} color={theme.colors.text} /> </TouchableOpacity> ), }} />

    VoiceIndicator, VoiceBackground, and VoiceTranscript receive the live voice state ('idle' | 'connecting' | 'listening' | 'thinking' | 'speaking') and the active theme; VoiceIndicator also receives isMuted (the user’s manual mute toggle, not the internal tool-flow mute — use it to dim or mark the indicator while the mic is off). VoiceMuteButton receives { isMuted, onToggle, theme, state }.

    Set voiceTranscript (on Qafka, not on voiceComponents) to control how the transcript area behaves: 'centered' (default) shows a single current line, 'chat' shows scrolling history of both speakers, 'off' hides transcripts entirely. See Configuration for the full type.

    One more slot exists for the built-in qafka_display chip tool used in voice: dataChipList, which renders the whole chip list (there’s no separate per-chip override — dataChip is declared on the VoiceComponents type but not wired up by the SDK). Override dataChipList the same way if you need custom chip styling.

    Tool Response Components

    When a tool result comes back, the widget renders it with a default Card / List / Detail / Table component based on the tool’s response.type. Override these by registering your own components and referencing them by name from the dashboard’s tool config.

    import { ProductCard, ProductRow } from './chat-renderers' <Qafka // projectId, endUserId, … components={{ ProductCard, ProductRow, }} />

    Each registered component receives { data, tool, theme, onAction }. In the dashboard, set the tool’s response.itemComponent to the matching key ("ProductCard", "ProductRow") to use your renderer instead of the default.

    Callbacks by Scenario

    The full callback list is in Configuration › Callbacks. Grouped by what you’re building:

    • Basic lifecycle: onReady, onError, onUnavailable (project suspended — chat screen stays mounted with a neutral message; use this to hide your own entry point).
    • Message flow: onMessageSent, onResponseReceived.
    • Navigation: onNavigationSuggest (always fires), onNavigationAction (fires on button press; omit to let the SDK navigate via Expo Router automatically). See Navigation.
    • External actions: onExternalSuggestion — WhatsApp, phone, map, app store, etc. See External Navigation.
    • Tools: onToolSuggested, onActionResult, onStepCompleted, onFileUploadRequest, onExtractionResult, onToolDataRequested. See Handling Tools.
    • Card CTAs: see below.

    Card CTAs

    When a tool is configured to use the Card renderer in the dashboard (see Tools — UI Rendering), buttons inside the card fire one of seven action types. Only three have a built-in fallback when you don’t wire a callback; the other four — including external_navigation and suggest_message — are silently skipped (no dev-mode warning) until you provide the matching callback.

    Actions with a built-in fallback

    Action typeBehavior with no callbackCallback to override it
    copyCopies the value to the clipboard.onCardCopy
    dismissHides the card locally.—
    shareOpens the OS share sheet.onCardShare

    Actions that need a host callback

    Action typeCallbackBehavior with no callback
    external_navigationonCardExternalNavigationSkipped — nothing happens.
    suggest_messageonCardSuggestMessageSkipped — nothing happens.
    deep_linkonCardDeepLinkSkipped — nothing happens.
    tool_triggeronCardToolTriggerSkipped — nothing happens.

    deep_link — internal route navigation

    Card author writes the path in the dashboard (with optional {{template}} placeholders that resolve at click time):

    { "component": "QButton", "label": "View details", "variant": "primary", "action": { "action": { "type": "deep_link", "path": "/store/{{id}}" } } }

    Partner registers the handler:

    import { useRouter } from 'expo-router' const router = useRouter() <Qafka // ...other props onCardDeepLink={(path) => router.push(path)} />

    When the partner doesn’t provide onCardDeepLink, the action is silently skipped — no warning is logged, in development or otherwise.

    tool_trigger — fire another tool from a card

    For chains where one card spawns another (e.g. “Confirm booking” button calls a bookAppointment tool), the SDK does not execute the tool for you — forward it to your own tool execution path, or resend it as a normal chat message:

    <Qafka // projectId, endUserId, … ref={qafkaRef} onCardToolTrigger={(toolName, params, meta) => { qafkaRef.current?.sendMessage(`Run ${toolName}`) }} />

    meta.ctaDisplayMode is 'user_message' (default) or 'silent' — controls whether the trigger appears as a user bubble in chat history.

    CTA telemetry

    onCardCTAClick fires once per button press. Event shape:

    { event: 'cta_click', cardTemplateId: string, cardSlug: string, actionType: 'external_navigation' | 'deep_link' | ..., toolName?: string, messageId?: string, itemIndex?: number, // only set in list mode item: unknown, // the bound record (item in list mode, full result in detail mode) confirmed: boolean, }

    item carries the actual record the partner is acting on, so you don’t need to keep the source array around to look it up by index. Forward it to your analytics service:

    <Qafka // projectId, endUserId, … onCardCTAClick={(event) => { analytics.track('card_cta_click', { action: event.actionType, template: event.cardTemplateId, itemId: (event.item as any)?.id, }) }} />
    Last updated on October 1, 2026
    WordPressHeadless SDK

    © 2026 Qafka Labs OÜ