assistant-ui/skills

react-native

Build assistant-ui chat experiences for Expo and bare React Native with @assistant-ui/react-native and @assistant-ui/ai-sdk.

View source
Original skill document

Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.

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

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 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 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
from this repository

More skills

All skills
assistant-ui
Community

assistant-ui

Overview and router for assistant-ui, the React library for building AI chat interfaces from composable primitives and a styled elements catalog. Use for high-level, cross-cutting, or architecture questions: choosing packages, picking a runtime, or understanding the layers (elements, primitives, the aui client with AuiConfig and AuiProvider, the runtime, adapters) and the message model. Covers @assistant-ui/react 0.15.x, the framework-neutral @assistant-ui/ai-sdk integration for AI SDK v7 (useChatRuntime, AssistantChatTransport; @assistant-ui/react-ai-sdk re-exports it), @assistant-ui/core, @assistant-ui/store, assistant-stream, assistant-cloud, the adapters for LangGraph, LangChain, Google ADK, A2A, AG-UI, Eve, OpenCode, and Pi, and the platform bindings @assistant-ui/react-native and @assistant-ui/react-ink; AssistantRuntimeProvider; the primitives ThreadPrimitive, MessagePrimitive, ComposerPrimitive; the hooks useAui, useAuiState, useAuiEvent; and runtime selection across useChatRuntime, useExternalStoreRuntime, useLangGraphRuntime, useLocalRuntime. For a specific area route to a focused sibling instead: setup, elements, primitives, runtime, tools, generative-ui, streaming, cloud, thread-list, copilots, markdown, react-mcp, observability, react-native, ink, or update.

installs
1
GitHub stars
26
Updated
Sep 4
assistant-ui
Community

cloud

Adds AssistantCloud backed persistence, authorization, and telemetry to assistant-ui apps. Use when wiring cross-session thread and message history, multi-device chat, message feedback, file uploads, or auth: passing cloud to useChatRuntime from @assistant-ui/ai-sdk, AISDKThreads({ cloud }) for AuiConfig hosts, the standalone useCloudChat/useThreads hooks from @assistant-ui/cloud-ai-sdk, or cloud on useLangGraphRuntime. Covers constructing AssistantCloud with authToken (JWT), apiKey plus userId/workspaceId (server-side), or anonymous; direct provider integrations (Clerk, Auth0, Supabase, Firebase) and a backend token endpoint; and the client surface verified against source: cloud.threads.{list,get,create,update,delete}, cloud.threads.messages.{list,create,update,feedback}, cloud.files.{generatePresignedUploadUrl,pdfToImages}, cloud.runs.{stream,report}, cloud.projects.threads, cloud.auth.tokens.create, and cloud.telemetry. Also covers a custom ThreadHistoryAdapter built on CloudMessagePersistence/createFormattedPersistence, run telemetry (beforeReport, sub-agent tracking with wrapSamplingHandler), and the NEXTPUBLICASSISTANTBASEURL/ASSISTANTAPIKEY env vars. Route here for threads that do not persist, 401s against the cloud API, or feedback buttons that do not save. For the sidebar UI itself use thread-list; for the general RemoteThreadListAdapter/ThreadHistoryAdapter contract use runtime.

installs
1
GitHub stars
26
Updated
Sep 4
assistant-ui
Community

copilots

Grounding an assistant in your app with assistant-ui copilots (@assistant-ui/react). Use when steering assistant behavior with useAssistantInstructions, feeding lazy send-time app state through useAssistantContext({ getContext }), exposing rendered components to the assistant with makeAssistantVisible(Component, { clickable, editable }), giving the model two-way component state through interactables (unstableuseInteractable for app-scoped panels, unstableinteractableTool inside defineToolkit for thread-scoped artifacts, both mounted via AuiConfig({ unstableinteractables: unstableInteractables() })), registering instructions and tools together imperatively with aui.modelContext.register({ getModelContext }), or bridging model context across an iframe boundary with AssistantFrameProvider and useAssistantFrameHost. The legacy Interactables() scope, useAssistantInteractable, and useInteractableState are deprecated since 2026-06-14 and scheduled for removal on or after 2026-09-14; new code uses the unstable API. Reach for this when the assistant should read the current page, click or edit UI, read and update component state through auto-generated update{name} tools, or receive tools and instructions from a sandboxed iframe. For LLM tools and tool-call UI use the tools skill; for runtime and thread state use the runtime skill.

installs
1
GitHub stars
26
Updated
Sep 4
assistant-ui
Community

elements

Installs and customizes assistant-ui elements, the styled shadcn-style component catalog at assistant-ui.com/elements served from the r.assistant-ui.com registry through npx assistant-ui@latest add . Use when adding a prebuilt chat surface or widget (Thread, ThreadList, AssistantModal, AssistantSidebar, ToolFallback, ToolGroup, MarkdownText, Reasoning, Sources, Attachment, ModelSelector, Voice orb, McpConfig) or one of the 120 standalone elements (approval card, agent plan, code diff, data table, chart, trace waterfall, message queue, composer variants, and so on), choosing between runtime-connected .aui.tsx files and props-driven standalone files, overriding Thread slots through the components prop, editing the copied source under components/assistant-ui/elements/, using the shared surfaces.tsx tokens, or picking the Radix versus Base UI flavor through the style-aware registry URL in components.json. Route here when an import from @/components/assistant-ui/... fails, an element renders unstyled, or the CLI installs the wrong flavor. For unstyled building blocks use primitives; for the CLI scaffold itself use setup.

installs
1
GitHub stars
26
Updated
Sep 4