---
title: "Svelte"
description: "Install @gistui/svelte, render a streamed answer in Svelte 5 or SvelteKit, and use your own Svelte 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.

# Svelte

`@gistui/svelte` is for Svelte 5 and SvelteKit. The package ships its Svelte source, compiled by your app's build.

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

## Render an answer

```svelte
<script lang="ts">
  import "@gistui/styles/styles.css";
  import { GistUI } from "@gistui/svelte";

  let { answer, loading } = $props();
</script>

<GistUI source={answer} streaming={loading} onaction={(a) => console.log(a)} />
```

Pass `stream={response.body}` instead to let the component read a `ReadableStream`. Without a component, the `use:gistui` action renders into any element.

## With SvelteKit

```ts title="src/routes/api/answer/+server.ts"
import { prompt } from "@gistui/catalog";
import { textDeltas, toReadable } from "@gistui/server";
import { OPENAI_API_KEY } from "$env/static/private";

export async function POST({ request }) {
  const { messages } = await request.json();
  const res = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: { Authorization: `Bearer ${OPENAI_API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ model: "gpt-5", stream: true, messages: [{ role: "system", content: prompt().text }, ...messages] }),
  });
  if (!res.ok) return new Response(await res.text(), { status: res.status });
  return new Response(toReadable(textDeltas(res, "openai")));
}
```

## Props and callbacks

Props: `source` + `streaming` or `stream`, `inline`, `theme`, `color`, `tokens`, `darkTokens`, `classNames`, `tools`, `mutations`, `allowedHosts`, `paused`, `initialState`, `lockUntil`, `openLinks`, `autofix` (default `true`), `components`, `library`.

Callbacks are lower-case, as Svelte 5 events are: `onaction`, `onerror`, `onstatechange`, `onprose`, `onautofix`, `ontoolcall`.

They mean the same as in every renderer: see [Renderer options](/docs/reference/renderer-options/).

## 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

Any Svelte component (shadcn-svelte, Bits UI, your design system) can replace a built-in one.

```svelte title="MyCard.svelte"
<script lang="ts">
  import { GistChildren, type GistUIComponentProps } from "@gistui/svelte";
  let { props }: GistUIComponentProps = $props(); // what the model wrote
</script>

<section class="my-card"><GistChildren /></section>
```

```svelte
<GistUI source={answer} components={{ Card: MyCard }} />
```

Your components see the Svelte context around `<GistUI>`. For a form field, `gistField(() => p.ctx)` joins the GistUI Form around it (`field.current.name`, `error`, `locked`, `value`, `setValue`).

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