Elements

Chart

Area, line, and bars, with points landing one at a time as the series streams in.

Runs this week+34%
92
fig. 01 · plays once, replay from the corner

Installation

npx shadcn@latest add "@assistant-ui/elements-chart"
First time? Set up a runtime

Runtime components read their state from an assistant-ui runtime. Add one to an existing project:

npx assistant-ui@latest init

Then wrap your app in a runtime provider:

import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/ai-sdk";

export default function App() {
  const runtime = useChatRuntime({
    transport: new AssistantChatTransport({ api: "/api/chat" }),
  });

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      {/* your components */}
    </AssistantRuntimeProvider>
  );
}

The installation guide covers new projects, templates, and API routes.

A chart draws a label, a headline value, and an optional delta above a small SVG series, with points landing one at a time as they become available. With a runtime the series streams straight out of the tool call's arguments; standalone you control how much of it is visible.

Getting started

A chart's series usually comes from the same tool call that produces its headline value, so it renders through that tool's render field. Because args is a best-effort parse of the JSON the model is still writing, args.points holds however many elements have streamed in so far; pass its length straight through as visibleCount and the chart reveals points as they arrive.

Render the tool call

components/assistant-ui/elements/plot-metric-tool-ui.tsx
"use client";

import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
import { Chart } from "@/components/assistant-ui/elements/chart";

type PlotMetricArgs = { label: string; points: number[] };
type PlotMetricResult = { value: string; delta?: string };

export const PlotMetricToolUI: ToolCallMessagePartComponent<
  PlotMetricArgs,
  PlotMetricResult
> = ({ args, result }) => {
  const points = args.points ?? [];
  return (
    <Chart
      label={args.label ?? ""}
      value={result?.value ?? "…"}
      delta={result?.delta}
      points={points}
      visibleCount={points.length}
      variant="area"
    />
  );
};

Register the tool

app/toolkit.ts
import { defineToolkit } from "@assistant-ui/react";
import { z } from "zod";
import { PlotMetricToolUI } from "@/components/assistant-ui/elements/plot-metric-tool-ui";

export const toolkit = defineToolkit({
  plot_metric: {
    type: "frontend",
    description: "Plot a metric's recent history as a small chart.",
    parameters: z.object({ label: z.string(), points: z.array(z.number()) }),
    execute: async ({ label, points }) => summarize(label, points),
    render: PlotMetricToolUI,
  },
});
app/MyRuntimeProvider.tsx
import { AssistantRuntimeProvider, AuiConfig, Tools } from "@assistant-ui/react";
import { useChatRuntime } from "@assistant-ui/ai-sdk";
import { toolkit } from "./toolkit";

export function MyRuntimeProvider({ children }: { children: React.ReactNode }) {
  const runtime = useChatRuntime();
  const config = AuiConfig({ tools: Tools({ toolkit }) });
  return (
    <AssistantRuntimeProvider runtime={runtime} config={config}>
      {children}
    </AssistantRuntimeProvider>
  );
}

See Tool UI for backend-defined tools and approval gates.

Anatomy

<div data-slot="chart">
  <div>{/* label, delta */}</div>
  <span>{/* value */}</span>
  <svg>{/* baseline, then an area+line, a line, or bars depending on variant */}</svg>
</div>

visibleCount clamps to 1…points.length, so the chart always draws at least one point and never overruns the array. In the line and area variants only the last visible point gets a filled dot; in bars only the last visible bar is tinted, the rest read as muted. delta's color follows its own leading character: a minus sign or an en or em dash reads as falling and tints red, anything else reads as rising and tints green; omit delta to hide it entirely.

Examples

Choosing a variant

variant decides how the series draws. The label, value, and delta stay in the same place regardless.

VariantDraws
"area" (default)A filled gradient under the line, plus the line itself.
"line"The line only, no fill.
"bars"One bar per visible point instead of a line.
<Chart variant="bars" /* ... */ />

Restyle the chart

Both lanes take className on the root. The label and value read from the shared mono and paper tokens in surfaces.tsx; the series itself is drawn with fixed blue fill and stroke classes in chart.tsx, so recoloring the line or bars means editing that file directly.

<Chart className="max-w-none" /* ... */ />

API reference

Tool-call render props

PropTypeDescription
argsTArgsParsed arguments. Partial while the model is still streaming them.
argsTextstringRaw JSON argument text streamed by the model.
resultTResult | undefinedThe tool's return value once it completes. undefined while running.
statusToolCallMessagePartStatusstatus.type is "running", "requires-action", "complete", or "incomplete".
toolNamestringName of the tool the model called.
toolCallIdstringStable id for this invocation.
isErrorboolean | undefinedWhether result represents a tool execution error.

Register the renderer on a toolkit entry's render field and attach the toolkit with Tools({ toolkit }). See Tool UI for the full render-prop surface, including addResult, human tools, and approval gates.