appCN
← All components

Chat

Block
new

The AI chat block — a complete, drop-in conversation: streaming assistant bubbles, a docked composer, reasoning, and starter prompts.

Preview

Scan with Expo Go to run it live

Install Expo Goon Android or iPhone, scan, and the entire appCN gallery loads — real native motion & gestures, not a web shim.

Open in Expo Go Play Store — coming soon
https://expo.dev/preview/update?message=v1.0.0+initial&updateRuntimeVersion=1.0.0&createdAt=2026-05-28T20%3A07%3A21.247Z&slug=exp&projectId=a2d02caa-be26-436a-acd6-f3007862ba0a&group=3ca7e750-9506-4146-8394-1a16c3a917a8

Install

npx @app-cn/cli@latest add chat

Recommended. Configures NativeWind + Reanimated and registers @app-cn on first run.

Anatomy

A full chat screen that composes StreamBubble (assistant turns), PromptInput (the composer), and ReasoningTrace (inline chain-of-thought) over a scrolling message list with user bubbles. Controlled by `messages` + `onSendMessage`. The header is a slot — default thin bar, `null` for immersive, or your own node. Reach for it when you want a finished AI chat instead of wiring the primitives by hand.

The delight detail

Magnetic stream-follow — while the assistant streams, the list eases to keep the newest line in view and releases the moment you scroll up; a scroll-to-bottom pill springs you back with a soft overshoot, and a success haptic fires the instant a reply settles.

Props

NameTypeDefaultDescription
messages*ChatMessage[]The conversation. Each message has id, role, content, and (assistant-only) status/reasoning/tools.
onSendMessage*(text: string) => voidCalled with the trimmed text when the user sends.
generatingbooleanfalseWhile true, the composer shows the stop control.
onStop() => voidCalled when the user taps stop while generating.
onRetry(id: string) => voidRetry a failed (status: "error") assistant message.
onRegenerate(id: string) => voidRegenerate an assistant message from its actions row.
startersstring[]Empty-state suggested prompts; tapping one calls onSendMessage.
placeholderstring"Message appCN…"Composer placeholder.
headerReact.ReactNodeHeader slot. undefined → default thin header; null → no header; node → custom.
titlestring"appCN"Title shown in the default header and empty state.
statusLabelstringStatus line under the title in the default header (e.g. "Ready").
avatarReact.ReactNodeAvatar content for the default header disc.
onNewChat() => voidWhen provided, the default header shows a + action that calls this.
emptyStateReact.ReactNodeOverride the default empty state entirely.
classNamestringExtra NativeWind classes on the root container.

Examples

Basic

A controlled chat. You own the messages and append the assistant's reply.

const [messages, setMessages] = React.useState<ChatMessage[]>([]);
const [generating, setGenerating] = React.useState(false);

const send = (text: string) => {
  setMessages((m) => [...m, { id: `u${Date.now()}`, role: "user", content: text }]);
  setGenerating(true);
  const id = `a${Date.now()}`;
  setMessages((m) => [...m, { id, role: "assistant", content: "Sure — here's the answer.", status: "streaming" }]);
  setTimeout(() => {
    setMessages((m) => m.map((x) => (x.id === id ? { ...x, status: "done" } : x)));
    setGenerating(false);
  }, 1800);
};

return (
  <Chat
    messages={messages}
    generating={generating}
    onSendMessage={send}
    starters={["What can you build?", "Show me a button", "Explain reanimated"]}
  />
);

Immersive (no header)

Pass header={null} for an edge-to-edge, chrome-free conversation.

<Chat header={null} messages={messages} onSendMessage={send} />

Accessibility

  • Every control (composer, send/stop, starter chips, scroll-to-bottom pill, copy/regenerate, retry) has an accessibilityRole and label, hitSlop, and visible press feedback.
  • Bubbles use accessibilityRole="text" so each message is announced as one unit.
  • Status (thinking/streaming/done/error) is conveyed by labels and text, never color alone.
  • Honors useReducedMotion() — entrance animations and the magnetic follow fall back to instant positioning.
  • The composer rises above the keyboard (KeyboardAvoidingView) and respects the home indicator via safe-area insets.