---
title: "React"
description: "Install @gistui/react, render a streamed answer in React or Next.js, handle actions, and swap in your own components."
---

> Documentation Index
> Fetch the complete documentation index at: https://gistui.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# React

`@gistui/react` renders GistUI programs with React 19 components. It works in Next.js, Vite, Remix and any other React setup.

```sh
npm i @gistui/react @gistui/styles @gistui/catalog
yarn add @gistui/react @gistui/styles @gistui/catalog
pnpm add @gistui/react @gistui/styles @gistui/catalog
bun add @gistui/react @gistui/styles @gistui/catalog
```

## Render an answer

```tsx
import "@gistui/styles/styles.css";
import { GistUI } from "@gistui/react";
import { ui } from "@gistui/react/ui";

<GistUI library={ui} stream={response.body} onAction={(a) => console.log(a)} />;
```

There are two ways to feed it. Use one or the other.

| Input | When |
|---|---|
| `stream` | A `ReadableStream` or async iterable of text or bytes, for example `response.body`. The component owns the read loop. |
| `source` + `streaming` | A growing string you hold in state, for example from `useChat`. Only the appended part is parsed. |

```tsx
<GistUI library={ui} source={text} streaming={isLoading} onAction={handle} />
```

## With Next.js (App Router)

`<GistUI>` is a client component. Put it in a file with `"use client"`, and build the prompt in a route handler so it never ships to the browser.

```ts title="app/api/answer/route.ts"
import { prompt } from "@gistui/catalog";
import { convertToModelMessages, streamText } from "ai";

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({ model: "openai/gpt-5", system: prompt().text, messages: await convertToModelMessages(messages) });
  return result.toTextStreamResponse();
}
```

```tsx title="app/answer.tsx"
"use client";
import "@gistui/styles/styles.css";
import { GistUI } from "@gistui/react";
import { ui } from "@gistui/react/ui";

export function Answer({ stream }: { stream: ReadableStream }) {
  return <GistUI library={ui} stream={stream} />;
}
```

A server render (`renderToString`) shows the content but calls no tool; data loads after mounting.

## Props

| Prop | Type | Notes |
|---|---|---|
| `library` | `Library` | `ui` from `@gistui/react/ui`, or your extended one. Required. |
| `stream` | `ReadableStream \| AsyncIterable` | Text or bytes. |
| `source`, `streaming` | `string`, `boolean` | A growing string and whether it is still growing. |
| `inline` | `boolean` | The text is chat text with `gistui` code fences. See [Chat](/docs/guides/chat/). |
| `onAction` | `(action) => void` | Button presses, form submits, `@emit`, errors. See [Actions and forms](/docs/guides/actions-and-forms/). |
| `onError`, `onStateChange`, `onProse`, `onAutofix` | callbacks | Diagnostics, state changes, prose lines, and what autofix changed. |
| `theme` | `"system" \| "light" \| "dark"` | Default `"system"`. |
| `color` | preset name | `neutral`, `slate`, `stone`, `rose`, `pink`, `violet`, `indigo`, `blue`, `teal`, `green`, `orange`, `amber`, `red`. |
| `tokens`, `darkTokens` | theme tokens | Values from your own code. See [Theming](/docs/guides/theming/). |
| `classNames` | per-component classes | For Tailwind utilities. |
| `initialState` | object | Starting values for `$state` variables. |
| `lockUntil` | `"done" \| "ready"` | When interactive parts unlock. Default `"done"` (the end of the stream). |
| `openLinks` | `boolean` | `false` stops `@open` from opening links itself. |
| `autofix` | `boolean` | Default `true`. See [Autofix and repair](/docs/guides/autofix/). |

## Tools and safety

A program is untrusted: a model wrote it, possibly steered by content it read. It cannot run code.

| Option | What it does |
|---|---|
| `tools` | Read-only functions (or an MCP-style client, `{ callTool }`). `@query` may call them as soon as the UI renders, so nothing here may change anything. |
| `mutations` | Functions that change something. They run only when a person presses a button. |
| `onToolCall` | Called before every tool call with `{ name, args, kind, nodeId }`. Return `false` to block it. |
| `allowedHosts` | Hosts that images, video and backgrounds may load from, for example `["cdn.example.com", "*.example.com"]`. Set it when the model sees private data. |
| `paused` | Stops timed data refreshes. |

More in [Security](/docs/guides/security/) and [Tools and live data](/docs/guides/tools-and-data/).

## Your own components

Replace any built-in component and keep its name. The prompt does not change, so the model's output does not either.

```tsx
import type { ComponentProps } from "@gistui/react";
import { ui, useGistButton, useGistField } from "@gistui/react/ui";

function MyInput(p: ComponentProps) {
  const field = useGistField(p); // name, label, required, error, locked, value, setValue
  return <Input name={field.name} disabled={field.locked} aria-invalid={!!field.error} />;
}
function MyButton(p: ComponentProps) {
  const b = useGistButton(p); // do: actions, submit and draft in forms, opens: dialogs, links
  return <><Button {...b.buttonProps}>{b.label}</Button>{b.dialog}</>;
}

const lib = ui.extend({ Input: MyInput, Button: MyButton });
```

See [Custom components](/docs/guides/custom-components/) and [UI libraries](/docs/ui-libraries/).

## What loads when

Measured gzip, React excluded: the runtime is 32 KB; the default components' first load is 74 KB of JavaScript plus a 29 KB stylesheet. Charts, tables, slides, pickers, code, math and diagrams load on demand.

Source: https://gistui.com/docs/frameworks/react/index.mdx
