Installation

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

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.

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

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

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.

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:

app/api/chat/route.ts
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();
}

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.

Set up the runtime

hooks/use-app-runtime.ts
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.

metro.config.js
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.

global.css
@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.

app/_layout.tsx
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/:

components.json
{
  "$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.

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.

app/index.tsx
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?