react-native

v2026.09.24

Build assistant-ui chat experiences for Expo and bare React Native with `@assistant-ui/react-native` and `@assistant-ui/ai-sdk`. Use when setting up an absolute mobile chat endpoint, `AssistantRuntimeProvider`, `useChatRuntime`, `AssistantChatTransport`, native `ThreadPrimitive`, `ComposerPrimitive`, `MessagePrimitive`, thread lists, attachments, React Native styles, `Metro` `withAui` for a `"use generative"` toolkit, local or remote thread adapters, or migrating a web chat surface to iOS and Android. Route here when a relative API URL fails on device, a native primitive lacks runtime state, `Metro` does not compile a toolkit, or web Elements are being used in a native app. Route web setup and DOM primitive work to `../setup/SKILL.md` and `../primitives/SKILL.md`.

GitHub
Install command
npx skhub add assistant-ui/react-native
Markdown
SKILL.md

assistant-ui React Native

Always consult assistant-ui.com/llms.txt for the latest API.

@assistant-ui/react-native supplies runtime-connected, unstyled React Native primitives. Use them with View, Text, Pressable, TextInput, FlatList, and native styles to build the chat surface. @assistant-ui/ai-sdk supplies the AI SDK v7 runtime and transport. The model route runs in a separate backend project, never inside the Expo bundle.

Contents

References

Quick start

Start from the maintained Expo example:

npx assistant-ui@latest create --example with-expo my-app
cd my-app

Set an endpoint that the app can reach. It must be an absolute URL. A physical device cannot resolve its own localhost to your development server.

EXPO_PUBLIC_CHAT_ENDPOINT_URL="https://api.example.com/api/chat"

Start Expo:

npx expo start

Manual setup

Install the native runtime and its AI SDK v7 peer packages in an existing Expo app:

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

Host the model route separately. The native app posts UI messages to that route through AssistantChatTransport; the route converts them asynchronously for AI SDK v7 and returns a UI message stream.

import { openai } from "@ai-sdk/openai";
import { convertToModelMessages, streamText } from "ai";

export async function POST(request: Request) {
  const { messages } = await request.json();
  const result = streamText({
    model: openai("gpt-5.6-luna"),
    messages: await convertToModelMessages(messages),
  });

  return result.toUIMessageStreamResponse();
}

Create the runtime in a hook. Keep the endpoint in the public Expo environment so the compiled app can reach it.

import {
  AssistantChatTransport,
  useChatRuntime,
} from "@assistant-ui/ai-sdk";

const chatEndpoint = process.env.EXPO_PUBLIC_CHAT_ENDPOINT_URL;

export function useAppRuntime() {
  if (!chatEndpoint) {
    throw new Error("EXPO_PUBLIC_CHAT_ENDPOINT_URL is required");
  }

  return useChatRuntime({
    transport: new AssistantChatTransport({ api: chatEndpoint }),
  });
}

AssistantChatTransport forwards frontend tool schemas and system messages. When the backend enables frontend tools, consume the request tools with frontendTools from @assistant-ui/ai-sdk; see tools for the shared backend contract.

Native chat composition

Put the runtime under the native AssistantRuntimeProvider, then compose the thread and composer from primitives. ThreadPrimitive.MessagesFlatList is the current list primitive and scopes each row to the corresponding message.

import {
  AssistantRuntimeProvider,
  AuiIf,
  ComposerPrimitive,
  MessagePrimitive,
  ThreadPrimitive,
  useAuiState,
} from "@assistant-ui/react-native";
import { Text, View } from "react-native";
import { useAppRuntime } from "./use-app-runtime";

function MessageRow() {
  const role = useAuiState((s) => s.message.role);

  return (
    <View
      style={{
        alignSelf: role === "user" ? "flex-end" : "flex-start",
        backgroundColor: role === "user" ? "#007aff" : "#f0f0f0",
        borderRadius: 16,
        margin: 8,
        padding: 12,
      }}
    >
      <MessagePrimitive.Content />
    </View>
  );
}

function Composer() {
  return (
    <ComposerPrimitive.Root style={{ flexDirection: "row", gap: 8, padding: 12 }}>
      <ComposerPrimitive.Input
        multiline
        placeholder="Message..."
        style={{ borderWidth: 1, borderRadius: 20, flex: 1, padding: 10 }}
      />
      <ComposerPrimitive.Send>
        <Text>Send</Text>
      </ComposerPrimitive.Send>
    </ComposerPrimitive.Root>
  );
}

function ChatScreen() {
  return (
    <ThreadPrimitive.Root style={{ flex: 1 }}>
      <AuiIf condition={(s) => s.thread.isEmpty}>
        <Text style={{ padding: 16 }}>Send a message to begin.</Text>
      </AuiIf>
      <ThreadPrimitive.MessagesFlatList autoScroll>
        {() => <MessageRow />}
      </ThreadPrimitive.MessagesFlatList>
      <Composer />
    </ThreadPrimitive.Root>
  );
}

export default function App() {
  const runtime = useAppRuntime();

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      <ChatScreen />
    </AssistantRuntimeProvider>
  );
}

MessagePrimitive.Content defaults text parts to native Text, not Markdown. Pass renderText or use MessagePrimitive.Parts with a React Native Markdown renderer when the model returns Markdown. For native keyboard handling, place the thread in a KeyboardAvoidingView and tune it for the platform.

Use useAuiState inside primitive scopes for reactive values. Use useAui() with no arguments for imperative actions such as aui.composer.send() and aui.thread.cancelRun(). Keep selectors to primitives or stable references instead of constructing an object or array in the selector.

Generative toolkits

Metro must compile files that start with "use generative". Install @assistant-ui/metro and wrap the default config. Expo gets getDefaultConfig from expo/metro-config; a bare React Native app gets it from @react-native/metro-config.

const { getDefaultConfig } = require("expo/metro-config");
const { withAui } = require("@assistant-ui/metro");

module.exports = withAui(getDefaultConfig(__dirname));

For a backendless toolkit, pass the aui option through the wrapper so frontend and human tool schemas remain uploadable:

module.exports = withAui({
  ...getDefaultConfig(__dirname),
  aui: { backendless: true },
});

Write the toolkit against @assistant-ui/react-native, including native View and Text renderers. The "use generative" directive makes the compiler infer tool kind from execute. The tools skill covers backend, frontend, human, provider, external, and stub tool semantics.

Common Gotchas

The mobile app posts to /api/chat and never reaches the server

  • Native apps have no browser origin for relative requests. Set EXPO_PUBLIC_CHAT_ENDPOINT_URL to the complete route URL.
  • Use a host reachable from the simulator or physical device. Device localhost is the device itself.

The provider is mounted but primitives throw or show no state

  • Create the runtime with useChatRuntime or another supported runtime hook, then pass it as runtime={runtime} to AssistantRuntimeProvider.
  • Render primitives below that provider. Message, part, attachment, queue, suggestion, and thread-list-item primitives also need their corresponding parent render scope.

A web Thread or an Elements import does not render in the Expo app

  • The shadcn Elements catalog is DOM and Tailwind based. Build the native UI with @assistant-ui/react-native primitives and React Native styles.

The message list does not stay at the bottom

  • Use ThreadPrimitive.MessagesFlatList with autoScroll for new screens. ThreadPrimitive.Messages is retained for compatibility and defaults its auto-scroll options to false.

Toolkits compile as ordinary modules or tool schemas never reach the model

  • Add withAui to Metro before using "use generative".
  • Import the identical generative module in the server build. If there is no server build, set aui: { backendless: true }.

Markdown appears as literal asterisks and fences

  • MessagePrimitive.Content uses native Text by default. Supply a native Markdown renderer through renderText.

Related Skills

  • setup -- create, initialize, and configure assistant-ui web projects
  • primitives -- DOM primitives for web applications, not React Native UI
  • runtime -- runtime behavior and backend transport concepts shared with native
  • tools -- generative toolkits, their backend route, and custom tool UI
Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

Not specified

Source path

assistant-ui/skills/react-native

Default branch

main

Latest commit

66befc4

Tree SHA

6a3f150