# Installation
URL: /docs/react-native

Get assistant-ui running in an Expo or React Native app with your first chat screen.

> For AI agents: a documentation index is available at [llms.txt](/llms.txt). Use `.md` for canonical markdown pages; `.mdx` is kept as a backwards-compatible alias on supported URL paths.

## Quick Start

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.

1. ### Create a new project

   ```
   npx assistant-ui@latest create --native my-chat-app
   cd my-chat-app
   ```

2. ### Configure API endpoint

   Create a `.env` file pointing to your chat API:

   ```
   EXPO_PUBLIC_CHAT_ENDPOINT_URL="http://localhost:3000/api/chat"
   ```

3. ### Start the app

   ```
   npx expo start
   ```

## Manual Setup

If you prefer to add assistant-ui to an existing Expo project, follow these steps.

1. ### Install dependencies

   ```bash
   npx expo install @assistant-ui/react-native @assistant-ui/ai-sdk
   ```

   > [!warn]
   >
   > **One assistant-ui package per app**
   >
   > 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`.

2. ### 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:

   Choose one:

   **OpenAI**

   ```
   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-6-luna"),
       messages: await convertToModelMessages(messages),
     });
     return result.toUIMessageStreamResponse();
   }
   ```

   **Anthropic**

   ```
   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-5"),
       messages: await convertToModelMessages(messages),
     });
     return result.toUIMessageStreamResponse();
   }
   ```

   **Google**

   ```
   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-3.8-flash"),
       messages: await convertToModelMessages(messages),
     });
     return result.toUIMessageStreamResponse();
   }
   ```

   > [!info]
   >
   > 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.

3. ### 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`,
       }),
     });
   }
   ```

4. ### Set up Uniwind

   The elements are styled with Tailwind classes through [Uniwind](https://uniwind.dev), 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";
   ```

5. ### 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` and the elements the thread renders (markdown text, reasoning, file, image, tool fallback), `components/ui/icon.tsx` and `lib/utils.ts`, and installs what they import (`lucide-react-native`, `react-native-svg`, `react-native-safe-area-context`, `react-native-marked`, `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.

6. ### Use it in your app

   Wrap your app with `AssistantRuntimeProvider` and render the thread. The [primitives](/docs/react-native/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>
     );
   }
   ```

## What's Next?

- [Elements](/docs/react-native/elements) — What ships natively, how each element installs, and what changes on a phone.
- [Add to an Existing App](/docs/react-native/existing-app) — Bring the elements into an app with its own runtime, styles and navigation.
- [Windowed History](/docs/react-native/history) — Load older messages above the window as the reader scrolls up.
- [Migration from Web](/docs/react-native/migration) — Already using assistant-ui? Migrate your web app to React Native.
- [Custom Backend](/docs/react-native/custom-backend) — Connect to your own backend API or manage threads server-side.
- [Primitives](/docs/react-native/primitives) — Composable native UI components for building chat interfaces.
- [Example App](https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-expo) — Full Expo example with drawer navigation, thread list, and styled UI.