showcase/shell-docs/src/content/reference/vue/hooks/useRenderTool.mdx
useRenderTool is a Vue 3 composable that registers a chat renderer for tool calls.
You can target a specific tool name (with a typed Zod parameters schema) or use a wildcard ("*") fallback renderer.
The render target is a Vue render function (returning a VNodeChild) or a Vue Component. It receives the tool name, the parsed parameters, the current status, and the result (when complete). This is not JSX; you render with Vue's h(), a setup-defined render function, or by passing a single-file Vue component.
This composable only handles rendering. It does not register a frontend handler.
<Callout type="info"> Call `useRenderTool` from inside `setup()` (or `<script setup>`) of a component mounted under [`CopilotKitProvider`](/reference/vue/components/CopilotKitProvider). It reads the active CopilotKit instance from provider context, so it cannot be called outside that tree. </Callout>import { useRenderTool } from "@copilotkit/vue/v2";
import { useRenderTool } from "@copilotkit/vue/v2";
import type { WatchSource } from "vue";
useRenderTool(
{
name: "*",
render: ((props) => VNodeChild) | Component,
agentId?: string,
},
deps?: WatchSource<unknown>[],
): void;
import { z } from "zod";
import { useRenderTool } from "@copilotkit/vue/v2";
import type { WatchSource } from "vue";
useRenderTool<S>(
{
name: string,
parameters: S, // a Zod / Standard Schema; parameters are inferred from it
render: ((props: RenderToolProps<S>) => VNodeChild) | Component<RenderToolProps<S>>,
agentId?: string,
},
deps?: WatchSource<unknown>[],
): void;
render receives a discriminated union (RenderToolProps) keyed on status. name and toolCallId are present in all states.
The three states are:
{ status: "inProgress", parameters: Partial<T>, result: undefined } -- arguments are still being streamed.{ status: "executing", parameters: T, result: undefined } -- arguments are fully resolved; the tool is executing.{ status: "complete", parameters: T, result: string } -- execution finished and a result is available.defineToolCallRenderer and registers it on the active CopilotKit instance via addHookRenderToolCall.args prop onto parameters before calling render, so your render function always sees parameters.agentId:name key (latest registration wins).watch, immediate: true) when name, agentId, or any value in deps changes. Include changing reactive captures in deps.<script setup lang="ts">
import { h } from "vue";
import { z } from "zod";
import { useRenderTool } from "@copilotkit/vue/v2";
useRenderTool({
name: "searchDocs",
parameters: z.object({ query: z.string() }),
render: (props) => {
if (props.status === "inProgress") {
return h("div", `Preparing ${props.name}...`);
}
if (props.status === "executing") {
return h("div", `Searching for: ${props.parameters.query}`);
}
return h("div", `Done: ${props.result}`);
},
});
</script>
<template>
<slot />
</template>
Pass a Vue component instead of a render function. The render props become the component's props.
<!-- SearchDocsRenderer.vue -->
<script setup lang="ts">
defineProps<{
name: string;
toolCallId: string;
status: "inProgress" | "executing" | "complete";
parameters: { query?: string };
result?: string;
}>();
</script>
<template>
<div v-if="status === 'inProgress'">Preparing {{ name }}...</div>
<div v-else-if="status === 'executing'">Searching for: {{ parameters.query }}</div>
<div v-else>Done: {{ result }}</div>
</template>
<!-- App.vue -->
<script setup lang="ts">
import { z } from "zod";
import { useRenderTool } from "@copilotkit/vue/v2";
import SearchDocsRenderer from "./SearchDocsRenderer.vue";
useRenderTool({
name: "searchDocs",
parameters: z.object({ query: z.string() }),
render: SearchDocsRenderer,
});
</script>
<template>
<slot />
</template>
Omit parameters and use name: "*" to provide a generic UI for any unhandled tool call.
<script setup lang="ts">
import { h } from "vue";
import { useRenderTool } from "@copilotkit/vue/v2";
useRenderTool({
name: "*",
render: (props) =>
h("div", `${props.status === "complete" ? "done" : "running"} ${props.name}`),
});
</script>
<template>
<slot />
</template>
depsdeps are Vue WatchSource values (refs or getters), not a dependency array. Pass them when render reads reactive state so the renderer re-registers on change.
<script setup lang="ts">
import { h, ref } from "vue";
import { z } from "zod";
import { useRenderTool } from "@copilotkit/vue/v2";
const locale = ref("en");
useRenderTool(
{
name: "searchDocs",
parameters: z.object({ query: z.string() }),
render: (props) =>
h("div", `[${locale.value}] ${props.name}: ${props.status}`),
},
[locale],
);
</script>
<template>
<slot />
</template>
defineToolCallRenderer -- build a renderer entry directlyuseFrontendTool -- register tools with handlersCopilotKitProvider -- provide the CopilotKit instance to the tree