Elements

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.

ElementInstall asWeb twin
Thread (composer, messages, action bars, branch picker)threadThread
Thread listthread-listThread list
Attachments (composer previews, message attachments, add button)attachmentAttachments
Markdown textmarkdown-textMarkdown text
Typing indicatorelements-typing-indicatorTyping indicator
Error stateelements-error-stateError state
Stopped runelements-stopped-runStopped run
Approval cardelements-approval-cardApproval card
Agent statuselements-agent-statusAgent status
Tool timelineelements-tool-timelineTool timeline
Icon buttonelements-icon-buttonnative 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 --fix

Each 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.json

The 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.

app/index.tsx
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.

components/assistant-ui/elements/tool-ui.tsx
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 take groupedIconButtonHitSlop so neighbours do not overlap, and text buttons take textButtonHitSlop; all three are exported from icon-button.tsx and surfaces.tsx for your own controls.
  • Announcements. accessibilityLiveRegion exists on Android and the web only, so state changes are announced through useAnnounce from surfaces.tsx, which calls AccessibilityInfo.announceForAccessibility. The approval card and the agent status announce changes, the error state and the typing indicator announce when they appear, and webLiveRegion adds the live region on the web build.
  • Reduce motion. useMotion reads the OS setting once per screen and the pulse, shimmer and typing dots stay still when it is on. The looping animations run with isInteraction: 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.MessagesFlatList keeps 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 theme with light and dark variants in your global.css; dark: classes and active: press states work as on the web. Web only classes are prefixed with web:.
  • 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 their aria-* form.

From web to React Native

Web elementReact Native
Thread, thread list, attachments, markdown textshipped
Typing indicator, error state, stopped run, approval card, agent status, tool timelineshipped, same props
Reasoning, tool group, follow up suggestions, sources, file, image, conversation mapnot yet; build from the primitives

The rest of the web catalog is DOM specific (hover, popovers, keyboard focus) and has no native plan.