The React Native elements, what each one installs, and what changes when the same design runs on a phone.
The React Native elements are the elements catalog rebuilt for Expo and React Native: the same names and visual language, and for the agent elements the same props, written against @assistant-ui/react-native and styled with Uniwind classes instead of DOM markup. They install the way shadcn components do, as source files in your project, so you can edit them once they land.
What ships
Every element below is a registry item in the native tree at https://r.assistant-ui.com/native/. The CLI resolves that tree automatically when it finds react-native in your package.json.
| Element | Install as | Web twin |
|---|---|---|
| Thread (composer, messages, action bars, branch picker) | thread | Thread |
| Thread list | thread-list | Thread list |
| Attachments (composer previews, message attachments, add button) | attachment | Attachments |
| Markdown text | markdown-text | Markdown text |
| Typing indicator | elements-typing-indicator | Typing indicator |
| Error state | elements-error-state | Error state |
| Stopped run | elements-stopped-run | Stopped run |
| Approval card | elements-approval-card | Approval card |
| Agent status | elements-agent-status | Agent status |
| Tool timeline | elements-tool-timeline | Tool timeline |
| Icon button | elements-icon-button | native only |
The shared pieces come along as registry dependencies: elements-surfaces (the class recipes, the monospace style, the pulse and announcement hooks), elements-range (count normalization), icon (the Lucide wrapper) and utils (cn).
Install
Add elements by name. The first install also needs the components.json from the installation guide, and every element expects Uniwind to be wired into Metro, because className on a React Native primitive does nothing without it.
npx assistant-ui@latest add elements-approval-card elements-agent-status
npx expo install --fixEach element lands in components/assistant-ui/elements/ next to the files it imports. The same items are reachable through shadcn directly when you want to pin the tree explicitly:
npx shadcn@latest add https://r.assistant-ui.com/native/elements-approval-card.jsonThe web catalog pages carry a React Native tab for every element that ships natively, with the element running inside a live Expo build and the install command above.
Thread slots
The thread element exposes the parts a host most often replaces through a components prop. Welcome, AssistantMessage and ToolFallback mirror the web thread. ComposerInput is the React Native addition: it swaps the text input of both the new message composer and the edit composer, which is how a host with its own rich text editor keeps the attachments row and the send button.
import { useAui, useAuiState } from "@assistant-ui/react-native";
import { Thread } from "@/components/assistant-ui/elements/thread.aui";
import { RichEditor } from "@/components/rich-editor";
function ComposerInput() {
const aui = useAui();
const text = useAuiState((s) => s.composer.text);
return (
<RichEditor
value={text}
onChangeText={(next) => aui.composer.setText(next)}
onSubmit={() => aui.composer.send()}
/>
);
}
export default function ChatScreen() {
return <Thread components={{ ComposerInput }} />;
}The agent elements
Approval card, agent status, tool timeline, stopped run and error state take plain props, so they render wherever your runtime puts the state. The usual home is a ToolFallback passed through the thread's components, where the tool call part carries toolName, args, status and, for server side approval gates, approval with respondToApproval.
import type { ToolCallMessagePartComponent } from "@assistant-ui/react-native";
import { AgentStatus } from "@/components/assistant-ui/elements/agent-status";
import { ApprovalCard } from "@/components/assistant-ui/elements/approval-card";
export const ToolUI: ToolCallMessagePartComponent = ({
toolName,
args,
status,
approval,
respondToApproval,
}) => {
if (approval && approval.approved === undefined) {
return (
<ApprovalCard
state="request"
title={`Run ${toolName}`}
subtitle="Needs your approval"
command={JSON.stringify(args)}
onAllowOnce={() => respondToApproval({ approved: true })}
onDeny={() => respondToApproval({ approved: false })}
/>
);
}
return (
<AgentStatus
state={status.type === "running" ? "working" : "done"}
label={toolName}
/>
);
};On a phone
The elements keep the web design but a few things are decided differently on a device.
- Touch targets. Every pressable reaches 48dp. Icon buttons take
iconButtonHitSlop, icon buttons that sit in a message footer row takegroupedIconButtonHitSlopso neighbours do not overlap, and text buttons taketextButtonHitSlop; all three are exported fromicon-button.tsxandsurfaces.tsxfor your own controls. - Announcements.
accessibilityLiveRegionexists on Android and the web only, so state changes are announced throughuseAnnouncefromsurfaces.tsx, which callsAccessibilityInfo.announceForAccessibility. The approval card and the agent status announce changes, the error state and the typing indicator announce when they appear, andwebLiveRegionadds the live region on the web build. - Reduce motion.
useMotionreads the OS setting once per screen and the pulse, shimmer and typing dots stay still when it is on. The looping animations run withisInteraction: false, so an indicator that never stops cannot block a list from rendering more rows. - Keyboard. The thread measures where it sits in the window and passes that offset to
KeyboardAvoidingView, so a navigation header above it does not hide the composer behind the keyboard. - Long histories.
ThreadPrimitive.MessagesFlatListkeeps the visible message anchored while older ones load above it; the thread relies on that default. - Styling. Tokens are the same
--color-*variables as the web kit, declared under@layer themewith light and dark variants in yourglobal.css;dark:classes andactive:press states work as on the web. Web only classes are prefixed withweb:. - Web export. The same files run under react-native-web, which is how the live showcase and the catalog previews are built. Attributes that react-native-web does not map, such as
accessibilityState, are written as theiraria-*form.
From web to React Native
| Web element | React Native |
|---|---|
| Thread, thread list, attachments, markdown text | shipped |
| Typing indicator, error state, stopped run, approval card, agent status, tool timeline | shipped, same props |
| Reasoning, tool group, follow up suggestions, sources, file, image, conversation map | not yet; build from the primitives |
The rest of the web catalog is DOM specific (hover, popovers, keyboard focus) and has no native plan.