---
title: "React Native"
description: "@guuey/chat/native renders the same transcript with React Native primitives — inverted list, same theme tokens; one required override: the view mount."
---

`@guuey/chat/native` is the React Native renderer over the **same** view-model as the web kit — the plan derivation, policies, presets, strings, and even the React hooks (`useTranscript`, `useTranscriptInputs`) are literally the same modules; only the walk differs. Guuey's own Portal apps (iOS/Android) render their chat through this tier.

Both `react` and `react-native` are optional peer dependencies — the root `@guuey/chat` subpath stays importable everywhere; this arm loads only inside an RN runtime. The package ships TypeScript source behind a `react-native` export condition, so Metro transpiles it with your app's Babel config — no bundler setup.

## The shape

```tsx
import { useMemo } from "react";
import { View, Text } from "react-native";
import { createWebAdapters } from "@guuey/agent-client";
import { useAgentInvoke } from "@guuey/agent-client/react";
import {
  NativeTranscript,
  useTranscript,
  useTranscriptInputs,
  type NativeTranscriptItemContext,
} from "@guuey/chat/native";
import { calmPolicy, type ViewMountItem } from "@guuey/chat";

// The one required override on native: the kit ships no WebView dependency.
function MyViewCard({ item }: { item: ViewMountItem; ctx: NativeTranscriptItemContext }) {
  return (
    <View>
      <Text>{item.toolTitle ?? "Generated view"}</Text>
      {/* mount the item's material in your WebView here */}
    </View>
  );
}

export function NativeChatScreen() {
  const adapters = useMemo(() => createWebAdapters(), []);
  const invoke = useAgentInvoke({
    endpointUrl: "https://your-agent-endpoint",
    appId: "your-app-id",
    adapters,
    preserveBlocks: true,
  });
  const { inputs } = useTranscriptInputs(invoke);
  const policy = useMemo(() => calmPolicy(), []);
  const transcript = useTranscript({ inputs, policy });

  return (
    <NativeTranscript
      plan={transcript.plan}
      strings={policy.strings}
      onToggle={transcript.toggle}
      onViewPhase={transcript.onViewPhase}
      resolvedMounts={transcript.resolvedMounts}
      components={{ view: MyViewCard }}
    />
  );
}
```

On RN you supply your own adapters in production (an AsyncStorage-backed thread store and a streaming `fetch` transport) in place of `createWebAdapters` — see the SDK's [React Native notes](/sdk/#beyond-react-on-the-web).

## The scroll contract, inverted

`NativeTranscript` renders an **inverted list** — the chat-app native idiom where content grows at the scroll origin. That makes the transcript scroll contract hold _by construction_: streaming stays pinned to the latest message, scrolling up never yanks the reader, and a reduce-motion-aware jump-to-latest affordance appears when you're away from the bottom. Windowing comes from the list primitive itself. Host chrome — keyboard avoidance, docks, insets — passes through a `listProps` escape hatch rather than being guessed at by the kit.

## The view mount is a required override

The native tier deliberately ships **without a WebView dependency** — adding one would force a native module choice on every consumer. So the `view` slot (generative-UI card mounts) is a required override on native: without one, the kit renders a labeled placeholder ("view unavailable"-style copy) — never a blank. Wrap your app's WebView of choice as the `view` component, as in the sample above; the display item hands you the resolved mount material and the producing tool's title.

## Theming

Same token schema as the web — [Theming](/chat-theming/) — projected to RN style values via `resolveNativeTheme` instead of CSS variables. `NativeTranscript` accepts the same `theme`/`mode` props; no stylesheet import exists or is needed on native.

## Web and native together

Because the view-model and hooks are shared, a product with both a web and an RN surface writes its transcript logic once: same policy, same strings, same overrides model — `components={{ tool: … }}` on web takes a DOM component, on native an RN one, and everything upstream of the renderer is identical.