Migration from Web

Migrate an existing @assistant-ui/react app to React Native.

If you already have an assistant-ui web app, most of your code transfers directly. The runtime core is shared between @assistant-ui/react and @assistant-ui/react-native via @assistant-ui/core. Only the UI layer changes.

What stays the same

  • Runtime setup — useChatRuntime, useLocalRuntime, ChatModelAdapter, and all runtime options work identically.
  • AI SDK integration — @assistant-ui/ai-sdk works with React Native. Your useChatRuntime + AssistantChatTransport setup transfers directly.
  • Tool definitions — Tools({ toolkit }) and toolkit renderers use the same API.
  • State hooks — useAuiState, useAui, and selector patterns are the same.
  • Backend code — Your API routes, streaming endpoints, and server-side logic need zero changes.

What changes

Web (@assistant-ui/react)React Native (@assistant-ui/react-native)
AssistantRuntimeProviderAssistantRuntimeProvider (same name)
DOM primitives (div, button, input)Native primitives (View, Pressable, TextInput)
CSS / Tailwind stylingTailwind classes through Uniwind in the registry elements; StyleSheet or inline styles on the primitives
Styled registry elements (shadcn components)Native registry elements, or your own UI built with primitives

Step-by-step

Install the React Native package

npx expo install @assistant-ui/react-native

Keep your runtime hook

Your useChatRuntime setup stays the same except for the api URL. A native build has no page origin to resolve /api/chat against, so pass an absolute URL:

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

Tip

If you use a monorepo, you can share this file between web and native projects directly.

Replace the provider

// Before (web)
// import { AssistantRuntimeProvider } from "@assistant-ui/react-native";

// After (React Native) — same component name, different package
import { AssistantRuntimeProvider } from "@assistant-ui/react-native";

export default function App() {
  const runtime = useAppRuntime();
  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {/* your native UI */}
    </AssistantRuntimeProvider>
  );
}

Rebuild the UI layer

This is the main migration effort. Web components (the thread and thread-list elements) don't render in React Native. Install their native counterparts from the registry, or build the screen from native primitives:

// Web — DOM-based
// import { Thread } from "@/components/assistant-ui/elements/thread.aui";

// React Native — use primitives or build custom
import {
  ThreadPrimitive,
  ComposerPrimitive,
  MessagePrimitive,
} from "@assistant-ui/react-native";
import { Text, View } from "react-native";

function MyMessage() {
  return (
    <View style={{ padding: 12 }}>
      <MessagePrimitive.Content />
    </View>
  );
}

function ChatScreen() {
  return (
    <ThreadPrimitive.Root style={{ flex: 1 }}>
      <ThreadPrimitive.Messages>
        {() => <MyMessage />}
      </ThreadPrimitive.Messages>
      <ComposerPrimitive.Root>
        <ComposerPrimitive.Input placeholder="Message..." />
        <ComposerPrimitive.Send>
          <Text>Send</Text>
        </ComposerPrimitive.Send>
      </ComposerPrimitive.Root>
    </ThreadPrimitive.Root>
  );
}

See the Primitives reference for the full list of available components.

Monorepo code sharing

In a monorepo, you can share everything except the UI layer:

packages/
  shared/
    hooks/          ← useAppRuntime with useChatRuntime (shared)
    tools/          ← tool definitions (shared)
  web/
    components/     ← DOM-based UI
  native/
    components/     ← React Native UI

The runtime hook, tool definitions, and backend code are platform-agnostic. Only the UI components need separate implementations for web and native.