Build AI chat interfaces for iOS and Android with @assistant-ui/react-native.
Quick Start
The fastest way to get started with assistant-ui for React Native.
Create a new project
npx assistant-ui@latest create --example with-expo my-chat-app
cd my-chat-appConfigure API endpoint
Create a .env file pointing to your chat API:
EXPO_PUBLIC_CHAT_ENDPOINT_URL="http://localhost:3000/api/chat"Start the app
npx expo startManual Setup
If you prefer to add assistant-ui to an existing Expo project, follow these steps.
Install dependencies
npx expo install @assistant-ui/react-native @assistant-ui/react-ai-sdkSetup Backend Endpoint
Create a backend API route using the Vercel AI SDK. This is the same endpoint you'd use with @assistant-ui/react on the web:
import { openai } from "@ai-sdk/openai";
import { convertToModelMessages, streamText } from "ai";
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: openai("gpt-4o-mini"),
messages: await convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse();
}import { anthropic } from "@ai-sdk/anthropic";
import { convertToModelMessages, streamText } from "ai";
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: anthropic("claude-sonnet-4-20250514"),
messages: await convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse();
}import { google } from "@ai-sdk/google";
import { convertToModelMessages, streamText } from "ai";
export const maxDuration = 30;
export async function POST(req: Request) {
const { messages } = await req.json();
const result = streamText({
model: google("gemini-2.0-flash"),
messages: await convertToModelMessages(messages),
});
return result.toUIMessageStreamResponse();
}This is the same backend you'd use with @assistant-ui/react on the web. If you already have an API route, you can reuse it as-is.
Set up the runtime
import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/react-ai-sdk";
const API_URL = process.env.EXPO_PUBLIC_API_URL ?? "http://localhost:3000";
export function useAppRuntime() {
return useChatRuntime({
transport: new AssistantChatTransport({
api: `${API_URL}/api/chat`,
}),
});
}Use it in your app
Wrap your app with AssistantRuntimeProvider and build your chat UI:
import {
AssistantRuntimeProvider,
useAuiState,
useAui,
} from "@assistant-ui/react-native";
import type { ThreadMessage } from "@assistant-ui/react-native";
import {
View,
Text,
TextInput,
FlatList,
Pressable,
KeyboardAvoidingView,
Platform,
} from "react-native";
import { useAppRuntime } from "@/hooks/use-app-runtime";
function MessageBubble({ message }: { message: ThreadMessage }) {
const isUser = message.role === "user";
const text = message.content
.filter((p) => p.type === "text")
.map((p) => ("text" in p ? p.text : ""))
.join("\n");
return (
<View
style={{
alignSelf: isUser ? "flex-end" : "flex-start",
backgroundColor: isUser ? "#007aff" : "#f0f0f0",
borderRadius: 16,
padding: 12,
marginVertical: 4,
marginHorizontal: 16,
maxWidth: "80%",
}}
>
<Text style={{ color: isUser ? "#fff" : "#000" }}>{text}</Text>
</View>
);
}
function Composer() {
const aui = useAui();
const text = useAuiState((s) => s.composer.text);
const isEmpty = useAuiState((s) => s.composer.isEmpty);
return (
<View
style={{
flexDirection: "row",
padding: 12,
alignItems: "flex-end",
}}
>
<TextInput
value={text}
onChangeText={(t) => aui.composer().setText(t)}
placeholder="Message..."
multiline
style={{
flex: 1,
borderWidth: 1,
borderColor: "#ddd",
borderRadius: 20,
paddingHorizontal: 16,
paddingVertical: 10,
maxHeight: 120,
}}
/>
<Pressable
onPress={() => aui.composer().send()}
disabled={isEmpty}
style={{
marginLeft: 8,
backgroundColor: !isEmpty ? "#007aff" : "#ccc",
borderRadius: 20,
width: 36,
height: 36,
justifyContent: "center",
alignItems: "center",
}}
>
<Text style={{ color: "#fff", fontWeight: "bold" }}>↑</Text>
</Pressable>
</View>
);
}
function ChatScreen() {
const messages = useAuiState(
(s) => s.thread.messages,
) as ThreadMessage[];
return (
<KeyboardAvoidingView
style={{ flex: 1 }}
behavior={Platform.OS === "ios" ? "padding" : "height"}
>
<FlatList
data={messages}
keyExtractor={(m) => m.id}
renderItem={({ item }) => <MessageBubble message={item} />}
/>
<Composer />
</KeyboardAvoidingView>
);
}
export default function App() {
const runtime = useAppRuntime();
return (
<AssistantRuntimeProvider runtime={runtime}>
<ChatScreen />
</AssistantRuntimeProvider>
);
}What's Next?
Migration from WebAlready using assistant-ui? Migrate your web app to React Native.Custom BackendConnect to your own backend API or manage threads server-side.PrimitivesComposable native UI components for building chat interfaces.Example AppFull Expo example with drawer navigation, thread list, and styled UI.