Native React Native chat UI for Chatty — zero WebView, zero compromise.
Drop a fully native, on-brand support chat into any React Native or Expo app in minutes. Talks
directly to the same /api/widget/* backend as the Chatty web widget, and renders every bubble,
avatar, and composer with real View/Text/FlatList components — no WebView, no JS bridge.
Install · Quick start · Design gallery · API reference · Example app
| No WebView, anywhere | Every bubble, avatar, and the composer are real RN components — no iframe, no JS bridge, no WebView memory/perf overhead. |
| Matches your dashboard automatically | Fetches the bot's theme and renders with the exact colors, corner radii, and launcher shape chosen in the dashboard — no manual styling. |
| Three integration shapes | A floating ChattyLauncher, an embedded ChattyChatView, or the headless useChattyChat hook. |
| A real composer, not a stub | Emoji picker and an animated attach menu, built in — camera/photo/mic wire up to whatever picker your app already uses. |
| Works with bare RN and Expo | No native linking required beyond @react-native-async-storage/async-storage, which most apps already have. |
npm install @personaliai/react-native @react-native-async-storage/async-storagereact and react-native are peer dependencies — install versions matching your app (react
18+, react-native 0.72+).
Find your bot ID in the Chatty dashboard under Embed & Integrate → React Native SDK.
Floating launcher (recommended) — a bubble that expands into a full-screen modal, the native equivalent of the web widget's launcher button:
import { ChattyLauncher } from "@personaliai/react-native";
export default function App() {
return (
<>
{/* ...your app... */}
<ChattyLauncher botId="YOUR_BOT_ID" position="right" />
</>
);
}Embedded full-screen chat — place it directly in your own navigation, e.g. as a "Support" screen:
import { ChattyChatView } from "@personaliai/react-native";
function SupportScreen() {
return <ChattyChatView botId="YOUR_BOT_ID" />;
}Headless — build your own UI
import { useChattyChat } from "@personaliai/react-native";
function CustomChat() {
const { messages, sendText, sending, theme } = useChattyChat({ botId: "YOUR_BOT_ID" });
// render messages and call sendText(text) yourself
}The SDK ships all 10 Chatty widget designs as color/radius tokens, ported 1:1 from the web
widget's globals.css, so a native screen looks like whatever design is chosen in the dashboard
rather than one generic look. No configuration required — ChattyChatView and ChattyLauncher
fetch the bot's theme and resolve the matching token set automatically, including legacy
widget_style IDs from older presets.
| Design | Accent |
|---|---|
minimal |
|
playful |
|
corporate |
|
dark-sleek |
|
gradient-glow |
|
glassmorphism |
|
ecommerce |
|
healthcare-calm |
|
neubrutalism |
|
luxury-editorial |
Font pairing (each web design uses a distinct Google Font) is intentionally out of scope for this release; color, radius, and header/bubble treatment carry most of a design's identity.
<ChattyLauncher
botId={string}
baseUrl={string} // optional, defaults to the production API
host={string} // optional, see Notes
position={"left" | "right"} // optional, defaults to "right"
onReady={() => void}
onMessage={(message) => void}
onVoiceCallPress={() => void}
onNotificationBellPress={() => void}
/>The button color follows the active design's accent automatically — same as web.
<ChattyChatView
botId={string}
baseUrl={string} // optional, defaults to the production API
host={string} // optional, see Notes
onReady={() => void}
onMessage={(message) => void}
onCameraPress={() => void} // optional, "Camera" tapped in the attach menu
onPhotoLibraryPress={() => void} // optional, "Photo Library" tapped in the attach menu
onAttachPress={() => void} // optional fallback if the two above aren't given
onMicPress={() => void} // optional — mic button only renders when this is set
onVoiceCallPress={() => void} // optional, header voice-call button (only shown when
// the bot's dashboard has voice enabled)
onNotificationBellPress={() => void} // optional, header notification-bell button — see Notes
onClose={() => void} // optional, renders a header close (✕) button.
// ChattyLauncher passes this for you.
/>Note
This SDK renders the composer's emoji picker and attach/mic UI (with layout animation), but it
deliberately doesn't bundle a camera, photo-library, or audio-recording dependency itself —
that would mean forcing every consumer (bare RN and Expo alike) to install and link a native
module they might not want. Instead, onCameraPress / onPhotoLibraryPress / onMicPress
fire when their button is tapped so you can wire up whichever picker/recorder your app already
uses (expo-image-picker + expo-av, react-native-image-picker, etc.) and then call
sendImage / ChattyClient.transcribe() yourself. The header's clear-chat button (↺) is fully
built in and needs no wiring — it resets local messages and starts a fresh session.
const {
theme, ready, messages, sending, aiPaused, error,
sendText, sendImage, clearChat,
} = useChattyChat({ botId: string, baseUrl?: string, host?: string });Everything ChattyChatView uses internally — conversation state, polling, and the
send/sendImage/clearChat actions — with no UI attached, for apps that want to render their own
layout.
Security — bot_id and domain restriction
bot_id is not a secret — it's extractable from any client, web or mobile. Domain restriction
(allowed_domains in the dashboard) is enforced by the backend as a rate-limit tier, not a
hard reject: verified web traffic gets 30 msgs/60s per bot+IP, everything else (including all
mobile SDK traffic — there's no way for a native app to obtain a "verified" token the way a
browser's Referer allows) gets throttled to 5 msgs/120s. The host prop this SDK sends is
advisory only and isn't used for access control. If your bot is mobile-primary, leave
allowed_domains empty to get the normal 30/60s tier instead.
Notification bell — what it does and doesn't do
Tapping it requests POST_NOTIFICATIONS on Android 13+ (PermissionsAndroid, built into RN
core — no extra dependency) and then calls onNotificationBellPress. There's no cross-platform
JS API for the permission ask on iOS — request it yourself (e.g. via expo-notifications)
before/inside the callback. Either way, that's as far as this SDK goes: actually delivering a
push when a reply arrives while the app is backgrounded needs FCM/APNs wired up at the app level
(register the device token, send it to your backend, store it against the session/user, call
FCM/APNs when a message lands for a session that isn't actively polling) — none of that exists
yet, it's backend work in chatty-backend.
Voice-call button
Only shown when the bot's dashboard has voice enabled, and only fires onVoiceCallPress — this
SDK doesn't bundle a voice-call implementation (a separate LiveKit integration, out of scope
here).
- Lead capture and meeting booking happen conversationally (the assistant decides to ask/act) — there's no separate REST call to trigger them from the SDK.
- Polling for human-agent takeover messages runs every 4s while the chat is mounted, matching the web widget's behavior.
- Conversation history is persisted locally (
AsyncStorage), mirroring the web widget'slocalStoragecache, so a returning user sees their prior messages.
example/ is a minimal, runnable Expo app demonstrating all integration styles side
by side.
npm install && npm run build # build the SDK once
cd example
npm install
npm startThen press i / a / w to try it on iOS Simulator, Android emulator, or web.
- React 18+, React Native 0.72+ (Expo SDK 49+)
@react-native-async-storage/async-storage1.19+
Contributing — bug reports, design-parity fixes, and PRs are welcome.
Licensed under MIT © PersonaliAI