Quick Start#
The fastest way to get started with assistant-ui for React Native. The scaffold is an Expo Router app with the assistant-ui elements installed from the registry (thread and thread list, including the composer and attachment subcomponents) and the AI SDK runtime wired up.
Create a new project#
npx assistant-ui@latest create --native my-chat-app
cd my-chat-app
Configure 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 start
Manual Setup#
If you prefer to add assistant-ui to an existing Expo project, follow these steps.
Install dependencies#
<InstallCommand expo={["@.../react-native", "@.../ai-sdk"]} />
Install only `@assistant-ui/react-native` in a React Native app. `@assistant-ui/react` is the web distribution: it pulls Radix and the DOM-coupled primitives into a native bundle, and if the two packages ever resolve different versions of the shared core, the second copy of the store breaks `useAui`. The runtime, tools, model context and external store APIs are all exported from `@assistant-ui/react-native`.Setup 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:
<Tabs groupId="provider" items={["OpenAI", "Anthropic", "Google"]}>
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-5.6-luna"),
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-6"),
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();
}
Set up the runtime#
import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/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`,
}),
});
}
Set up Uniwind#
The elements are styled with Tailwind classes through Uniwind, using the same color tokens as the web kit. Install it together with Tailwind CSS:
npx expo install uniwind tailwindcss
Wrap your Metro config. If you ship "use generative" toolkits, install @assistant-ui/metro and wrap the config in its withAui inside withUniwindConfig, which has to stay outermost.
const { getDefaultConfig } = require("expo/metro-config");
const { withUniwindConfig } = require("uniwind/metro");
module.exports = withUniwindConfig(getDefaultConfig(__dirname), {
cssEntryFile: "./global.css",
dtsFile: "./uniwind-types.d.ts",
});
Create the CSS entry with the assistant-ui color tokens. Declare the colors directly as --color-* custom properties under @layer theme; Uniwind resolves those on native, while an @theme inline indirection only works on web.
@import "tailwindcss";
@import "uniwind";
@theme {
--radius-sm: 6px;
--radius-md: 8px;
--radius-lg: 10px;
--radius-xl: 14px;
--radius-2xl: 18px;
--radius-3xl: 24px;
}
@layer theme {
:root {
@variant light {
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.141 0.005 285.823);
--color-card: oklch(1 0 0);
--color-card-foreground: oklch(0.141 0.005 285.823);
--color-primary: oklch(0.21 0.006 285.885);
--color-primary-foreground: oklch(0.985 0 0);
--color-secondary: oklch(0.967 0.001 286.375);
--color-secondary-foreground: oklch(0.21 0.006 285.885);
--color-muted: oklch(0.967 0.001 286.375);
--color-muted-foreground: oklch(0.552 0.016 285.938);
--color-accent: oklch(0.967 0.001 286.375);
--color-accent-foreground: oklch(0.21 0.006 285.885);
--color-destructive: oklch(0.577 0.245 27.325);
--color-border: oklch(0.92 0.004 286.32);
--color-input: oklch(0.92 0.004 286.32);
--color-ring: oklch(0.705 0.015 286.067);
}
@variant dark {
--color-background: oklch(0.141 0.005 285.823);
--color-foreground: oklch(0.985 0 0);
--color-card: oklch(0.21 0.006 285.885);
--color-card-foreground: oklch(0.985 0 0);
--color-primary: oklch(0.92 0.004 286.32);
--color-primary-foreground: oklch(0.21 0.006 285.885);
--color-secondary: oklch(0.274 0.006 286.033);
--color-secondary-foreground: oklch(0.985 0 0);
--color-muted: oklch(0.274 0.006 286.033);
--color-muted-foreground: oklch(0.705 0.015 286.067);
--color-accent: oklch(0.274 0.006 286.033);
--color-accent-foreground: oklch(0.985 0 0);
--color-destructive: oklch(0.704 0.191 22.216);
--color-border: oklch(1 0 0 / 10%);
--color-input: oklch(1 0 0 / 15%);
--color-ring: oklch(0.552 0.016 285.938);
}
}
}
Import it once at the root of the app, and add uniwind-types.d.ts to your tsconfig.json include so className type-checks on the React Native components.
import "../global.css";
Add the elements#
The elements install like shadcn components: the assistant-ui CLI detects react-native in your package.json and reads the native registry tree. Create a components.json first so the files land under components/ and lib/:
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "global.css",
"baseColor": "zinc",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
npx assistant-ui@latest add thread thread-list
npx expo install --fix
This writes components/assistant-ui/elements/{thread,attachment,thread-list}.aui.tsx, components/ui/icon.tsx and lib/utils.ts, and installs what they import (lucide-react-native, react-native-svg, react-native-safe-area-context, expo-clipboard, expo-image-picker, expo-image-manipulator). npx expo install --fix aligns those Expo modules with your SDK version. The thread reads safe area insets, so wrap the app in SafeAreaProvider from react-native-safe-area-context unless Expo Router already provides it.
Use it in your app#
Wrap your app with AssistantRuntimeProvider and render the thread. The primitives are there when you want to build your own elements instead.
import { AssistantRuntimeProvider } from "@assistant-ui/react-native";
import { Thread } from "@/components/assistant-ui/elements/thread.aui";
import { useAppRuntime } from "@/hooks/use-app-runtime";
export default function App() {
const runtime = useAppRuntime();
return (
<AssistantRuntimeProvider runtime={runtime}>
<Thread />
</AssistantRuntimeProvider>
);
}