---
title: "Vue"
description: "Install @gistui/vue, render a streamed answer in Vue 3.5+ or Nuxt, and use your own Vue 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.

# Vue

`@gistui/vue` is for Vue 3.5+ and Nuxt.

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

## Render an answer

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

defineProps<{ answer: string; loading: boolean }>();
function onAction(a) {
  console.log(a);
}
</script>

<template>
  <GistUI :source="answer" :streaming="loading" @action="onAction" />
</template>
```

Pass `:stream="response.body"` instead of `source` + `streaming` to let the component read a `ReadableStream` itself.

## With Nuxt

Build the prompt in a server route, and render inside `<ClientOnly>` if the page is server-rendered.

```ts title="server/api/answer.post.ts"
import { prompt } from "@gistui/catalog";
import { textDeltas, toReadable } from "@gistui/server";

export default defineEventHandler(async (event) => {
  const { messages } = await readBody(event);
  const res = await fetch("https://api.openai.com/v1/chat/completions", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ model: "gpt-5", stream: true, messages: [{ role: "system", content: prompt().text }, ...messages] }),
  });
  return sendStream(event, toReadable(textDeltas(res, "openai")));
});
```

## Props and events

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

Events: `action`, `error`, `stateChange`, `prose`, `autofix`.

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 Vue component (shadcn-vue, Reka UI, your design system) can replace a built-in one.

```vue title="MyCard.vue"
<script setup lang="ts">
import { GistChildren, type GistUIComponentProps } from "@gistui/vue";
defineProps<GistUIComponentProps>(); // props (what the model wrote), node, ctx
</script>

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

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

Your components see the app's plugins and whatever the components around `<GistUI>` provide. For a form field, `useGistField(props)` joins the GistUI Form around it: validation from the field's props, error messages and `bind:$var`.

```ts
const field = useGistField(props); // field.name, label, required, error, locked, value, setValue
```

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