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
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
Renderer configuration object with the following properties: Tool name to render. Use "*" for a wildcard renderer that handles any tool without a specific renderer. name : "searchProducts"
// or
name : "*" // Wildcard renderer
Zod schema defining the tool’s parameters. Required for named tools, omit for wildcard renderers. Provides type inference for the parameters prop in your render function. parameters : z . object ({
city: z . string (),
units: z . enum ([ "celsius" , "fahrenheit" ]),
})
render
(props: RenderToolProps) => React.ReactElement
required
Function that returns a React element to display for this tool call. Receives props based on the current execution status.
Constrain this renderer to a specific agent. If omitted, applies to all agents. agentId : "research-agent"
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:
Partial parameters (may be incomplete during streaming)
Indicates parameters are still being received
executing Status
When the tool is being executed:
Complete parameters (fully received)
Indicates tool is executing
complete Status
After the tool has finished executing:
Indicates tool execution finished
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.