Skip to main content

Overview

The useRenderTool hook registers custom UI renderers for tool calls that appear in the chat interface. Use this when you want to display rich, interactive components for tool executions without implementing the tool’s handler logic.

Usage

import { useRenderTool } from "@copilotkit/react";
import { z } from "zod";

function ToolRenderers() {
  useRenderTool({
    name: "searchDocs",
    parameters: z.object({
      query: z.string(),
    }),
    render: ({ status, parameters, result }) => {
      if (status === "executing") {
        return <div>Searching for "{parameters.query}"...</div>;
      }
      return <div>{result}</div>;
    },
  });
  
  return null;
}

Type Signatures

Named Tool Renderer

function useRenderTool<S extends z.ZodTypeAny>(
  config: {
    name: string;
    parameters: S;
    render: (props: RenderToolProps<S>) => React.ReactElement;
    agentId?: string;
  },
  deps?: ReadonlyArray<unknown>
): void

Wildcard Renderer

function useRenderTool(
  config: {
    name: "*";
    render: (props: any) => React.ReactElement;
    agentId?: string;
  },
  deps?: ReadonlyArray<unknown>
): void

Parameters

config
RenderToolConfig
required
Renderer configuration object with the following properties:
deps
ReadonlyArray<unknown>
Optional dependency array (like useEffect). When dependencies change, the renderer is re-registered.
useRenderTool(config, [theme, locale]);

Render Props

The render function receives different props based on the tool execution status:

inProgress Status

When the agent is still streaming tool call parameters:
name
string
Tool name
parameters
Partial<z.infer<S>>
Partial parameters (may be incomplete during streaming)
status
'inProgress'
Indicates parameters are still being received
result
undefined
No result yet

executing Status

When the tool is being executed:
name
string
Tool name
parameters
z.infer<S>
Complete parameters (fully received)
status
'executing'
Indicates tool is executing
result
undefined
No result yet

complete Status

After the tool has finished executing:
name
string
Tool name
parameters
z.infer<S>
Complete parameters
status
'complete'
Indicates tool execution finished
result
string
Tool execution result

Examples

Basic Renderer

import { useRenderTool } from "@copilotkit/react";
import { z } from "zod";

function WeatherRenderer() {
  useRenderTool({
    name: "getWeather",
    parameters: z.object({
      city: z.string(),
    }),
    render: ({ status, parameters, result }) => {
      if (status === "inProgress") {
        return <div>Preparing weather query...</div>;
      }
      
      if (status === "executing") {
        return (
          <div className="weather-loading">
            🌤️ Fetching weather for {parameters.city}...
          </div>
        );
      }
      
      return (
        <div className="weather-result">
          {result}
        </div>
      );
    },
  });
  
  return null;
}

Rich UI Renderer

import { useRenderTool } from "@copilotkit/react";
import { z } from "zod";

function ProductSearchRenderer() {
  useRenderTool({
    name: "searchProducts",
    parameters: z.object({
      query: z.string(),
      category: z.string().optional(),
      maxResults: z.number().optional(),
    }),
    render: ({ status, parameters, result }) => {
      if (status === "executing") {
        return (
          <div className="search-card">
            <div className="search-header">
              <span className="icon">🔍</span>
              <span>Searching products</span>
            </div>
            <div className="search-query">
              Query: "{parameters.query}"
              {parameters.category && ` in ${parameters.category}`}
            </div>
            <div className="spinner" />
          </div>
        );
      }
      
      if (status === "complete") {
        const products = JSON.parse(result);
        return (
          <div className="product-results">
            <div className="results-header">
              Found {products.length} products
            </div>
            <div className="product-grid">
              {products.map((product) => (
                <div key={product.id} className="product-card">
                  <img src={product.image} alt={product.name} />
                  <h4>{product.name}</h4>
                  <p className="price">${product.price}</p>
                </div>
              ))}
            </div>
          </div>
        );
      }
      
      return <div>Preparing search...</div>;
    },
  });
  
  return null;
}

Wildcard Renderer

A fallback renderer for any tool without a specific renderer:
import { useRenderTool } from "@copilotkit/react";

function DefaultToolRenderer() {
  useRenderTool({
    name: "*",
    render: ({ name, status, parameters, result }) => {
      if (status === "executing") {
        return (
          <div className="default-tool">
            <div className="tool-name">{name}</div>
            <div className="tool-status">⏳ Executing...</div>
          </div>
        );
      }
      
      if (status === "complete") {
        return (
          <div className="default-tool">
            <div className="tool-name">{name}</div>
            <div className="tool-result">✓ Complete</div>
          </div>
        );
      }
      
      return (
        <div className="default-tool">
          <div className="tool-name">{name}</div>
          <div className="tool-status">Preparing...</div>
        </div>
      );
    },
  });
  
  return null;
}

Conditional Rendering by Status

import { useRenderTool } from "@copilotkit/react";
import { z } from "zod";

function StatusBasedRenderer() {
  useRenderTool({
    name: "analyzeData",
    parameters: z.object({
      datasetId: z.string(),
      analysisType: z.string(),
    }),
    render: (props) => {
      const { status, parameters } = props;
      
      const statusConfig = {
        inProgress: {
          icon: "⏳",
          text: "Loading analysis request...",
          className: "loading",
        },
        executing: {
          icon: "🔄",
          text: `Running ${parameters.analysisType} analysis...`,
          className: "executing",
        },
        complete: {
          icon: "✅",
          text: "Analysis complete",
          className: "complete",
        },
      };
      
      const config = statusConfig[status];
      
      return (
        <div className={`analysis-status ${config.className}`}>
          <span className="icon">{config.icon}</span>
          <span className="text">{config.text}</span>
          {status === "complete" && (
            <div className="result">{props.result}</div>
          )}
        </div>
      );
    },
  });
  
  return null;
}

Agent-Specific Renderer

import { useRenderTool } from "@copilotkit/react";
import { z } from "zod";

function AgentSpecificRenderers() {
  // Research agent tool renderer
  useRenderTool({
    name: "searchPapers",
    parameters: z.object({ query: z.string() }),
    agentId: "research-agent",
    render: ({ status, parameters, result }) => (
      <div className="research-tool">
        <div className="agent-badge">Research Agent</div>
        {status === "executing" && (
          <div>📚 Searching academic papers: {parameters.query}</div>
        )}
        {status === "complete" && <div>{result}</div>}
      </div>
    ),
  });
  
  // Support agent tool renderer
  useRenderTool({
    name: "searchPapers",
    parameters: z.object({ query: z.string() }),
    agentId: "support-agent",
    render: ({ status, parameters, result }) => (
      <div className="support-tool">
        <div className="agent-badge">Support Agent</div>
        {status === "executing" && (
          <div>📖 Searching help docs: {parameters.query}</div>
        )}
        {status === "complete" && <div>{result}</div>}
      </div>
    ),
  });
  
  return null;
}

Renderer with Dependencies

import { useRenderTool } from "@copilotkit/react";
import { z } from "zod";
import { useTheme } from "./hooks/useTheme";

function ThemedRenderer() {
  const theme = useTheme();
  
  useRenderTool({
    name: "generateChart",
    parameters: z.object({
      data: z.array(z.number()),
      type: z.string(),
    }),
    render: ({ status, parameters, result }) => {
      const colors = theme === "dark" 
        ? { bg: "#1a1a1a", text: "#fff" }
        : { bg: "#fff", text: "#000" };
      
      if (status === "executing") {
        return (
          <div style={{ background: colors.bg, color: colors.text }}>
            Generating {parameters.type} chart...
          </div>
        );
      }
      
      if (status === "complete") {
        return (
          <div style={{ background: colors.bg, color: colors.text }}>
            <img src={result} alt="Generated chart" />
          </div>
        );
      }
      
      return <div>Preparing...</div>;
    },
  }, [theme]); // Re-register when theme changes
  
  return null;
}

Inline Parameters Display

import { useRenderTool } from "@copilotkit/react";
import { z } from "zod";

function DetailedRenderer() {
  useRenderTool({
    name: "queryDatabase",
    parameters: z.object({
      query: z.string(),
      database: z.string(),
      limit: z.number(),
    }),
    render: ({ status, parameters, result }) => (
      <div className="db-query">
        <div className="query-header">
          <span className="icon">🗄️</span>
          <span>Database Query</span>
        </div>
        
        <div className="query-details">
          <div><strong>Database:</strong> {parameters.database}</div>
          <div><strong>Query:</strong> {parameters.query}</div>
          <div><strong>Limit:</strong> {parameters.limit}</div>
        </div>
        
        {status === "executing" && (
          <div className="status executing">
            Executing query...
          </div>
        )}
        
        {status === "complete" && (
          <div className="query-result">
            <div className="status complete">✓ Query completed</div>
            <pre>{result}</pre>
          </div>
        )}
      </div>
    ),
  });
  
  return null;
}

Behavior

Registration

  • Renderers are registered when the component mounts
  • If a renderer with the same name and agentId exists, it is overridden
  • Renderers persist in the registry even after component unmounts
    • This allows historical tool calls in chat to continue rendering

Wildcard Renderers

  • Use name: "*" to create a fallback renderer
  • Wildcard renderers only apply when no specific renderer matches
  • Parameters are not typed for wildcard renderers (use any)

Deduplication

Renderers are deduplicated by the combination of:
  • agentId (or "" if not specified)
  • name
The latest registration wins if there are conflicts.

Use Cases

Tool Visualization

Display rich visualizations for data analysis, search results, or computation tools.

Progress Indicators

Show custom loading states and progress for long-running operations.

Structured Output

Format structured data (tables, lists, cards) in a user-friendly way.

Branding

Apply custom styling and branding to tool executions in the chat.

Interactive Results

Display interactive elements in tool results (though interaction is read-only in completed states).

Notes

Renderers persist after component unmount so that tool calls in chat history can still display custom UI. This differs from useHumanInTheLoop, which removes its renderer on unmount.
If you provide an inline object to useRenderTool, wrap it in useMemo to prevent unnecessary re-registrations:
const config = useMemo(() => ({
  name: "myTool",
  parameters: z.object({ ... }),
  render: ({ status, parameters, result }) => { ... },
}), [/* dependencies */]);

useRenderTool(config);
For tools with both handler and renderer, use useFrontendTool with the render property instead of using both hooks separately.