react-native · assistant-ui — Claude Code skill: assistant-ui chat in Expo
A skill for building assistant-ui chat surfaces in Expo and bare React Native: native primitives, the AI SDK runtime and a transport to your backend.
npx skills add assistant-ui/skills --skill react-native -a claude-codeWhat react-native does
The skill covers building a mobile chat on @assistant-ui/react-native and @assistant-ui/ai-sdk. The first package ships unstyled primitives wired to the runtime, the second brings the AI SDK v7 runtime and transport. You compose the surface from View, Text, Pressable, TextInput and FlatList.
The model route lives in a separate backend project, never inside the Expo bundle. The app posts UI messages through AssistantChatTransport, the route converts them asynchronously and returns a UI message stream. The endpoint has to be absolute: a physical device cannot resolve its own localhost to your dev server, so a relative URL fails on device. Keep the address in EXPO_PUBLIC_CHAT_ENDPOINT_URL.
Inside you get a quick start from the with-expo example, manual package install, thread and composer composition, generative toolkits through Metro withAui, and a list of common gotchas. MessagePrimitive.Content renders text parts as native Text by default rather than Markdown, so Markdown from the model needs a custom renderer. Web setup and DOM primitive work is routed to the neighbouring ../setup/SKILL.md and ../primitives/SKILL.md.
What it helps with
- build a chat screen in Expo with native primitives
- wire the AI SDK v7 runtime into a mobile UI
- fix a relative endpoint URL that fails on device
- get Metro to compile files starting with
"use generative" - move a web chat surface to iOS and Android
Example prompts for the agent
- Set up an assistant-ui chat in my Expo app using the native primitives
- Why does my relative chat endpoint URL fail on a physical device?
- Metro won't compile my generative toolkit — wrap the config with withAui
How to install react-native
1Skills CLI (Vercel)
npx skills add assistant-ui/skills --skill react-native -a claude-code2Manually: put the files in place
~/.claude/skills/react-native/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/react-native/
1Skills CLI (Vercel)
npx skills add assistant-ui/skills --skill react-native -a codex2Manually: put the files in place
.agents/skills/react-native/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add assistant-ui/skills --skill react-native -a cursor2Manually: put the files in place
.cursor/skills/react-native/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install assistant-ui/skills react-native2Manually: put the files in place
.github/skills/react-native/SKILL.md1Skills CLI (Vercel)
npx skills add assistant-ui/skills --skill react-native -a gemini-cli2Platform CLI
gemini skills install https://github.com/assistant-ui/skills --path assistant-ui/skills/react-nativeManually: put the files in place
.devin/skills/react-native/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add assistant-ui/skills --skill react-native -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about react-native
How do I install the react-native skill in Claude?
Run npx skills add assistant-ui/skills --skill react-native -a claude-code. The skill file lands at ~/.claude/skills/react-native/SKILL.md.
What does the react-native skill do?
It helps you build an assistant-ui chat in Expo or bare React Native: native primitives, the AI SDK v7 runtime and transport, generative toolkits, and migrating a web chat to mobile.
Why must the chat endpoint be an absolute URL?
A physical device cannot resolve its own localhost to your development server. Set the full address and keep it in EXPO_PUBLIC_CHAT_ENDPOINT_URL.
Source text
SKILL.md · MIT
---
name: react-native
description: "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`."
license: MIT
---
# assistant-ui React Native
**Always consult [assistant-ui.com/llms.txt](https://www.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](#references) | [Quick start](#quick-start) | [Manual setup](#manual-setup) | [Native chat composition](#native-chat-composition) | [Generative toolkits](#generative-toolkits) | [Common Gotchas](#common-gotchas) | [Related Skills](#related-skills)
## References
- [./references/primitives.md](./references/primitives.md) -- native primitive namespaces and their parts
- [./references/hooks.md](./references/hooks.md) -- state, runtime, tool, and scoped-provider hooks
- [./references/adapters.md](./references/adapters.md) -- local persistence, attachments, and remote thread lists
- [./references/custom-backend.md](./references/custom-backend.md) -- a streaming model adapter and a backend-owned thread list
- [./references/migration.md](./references/migration.md) -- moving a web runtime to a native UI layer
## Quick start
Start from the maintained Expo example:
```sh
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.
```dotenv
EXPO_PUBLIC_CHAT_ENDPOINT_URL="https://api.example.com/api/chat"
```
Start Expo:
```sh
npx expo start
```
## Manual setup
Install the native runtime and its AI SDK v7 peer packages in an existing Expo app:
```sh
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.
```ts
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.
```tsx
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](../tools/SKILL.md) 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.
```tsx
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`.
```js
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:
```js
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](../tools/SKILL.md) 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](../setup/SKILL.md) -- `create`, `initialize`, and configure assistant-ui web projects
- [primitives](../primitives/SKILL.md) -- DOM primitives for web applications, not React Native UI
- [runtime](../runtime/SKILL.md) -- runtime behavior and backend transport concepts shared with native
- [tools](../tools/SKILL.md) -- generative toolkits, their backend route, and custom tool UI
© assistant-ui · MIT · assistant-ui/skills
Similar skills
Everything for Mobile →react-native by other authors comes first.