Elements

Data table

A small comparison table the model can answer with directly.

ModelContextCost
SSonnet 4.5200k$3.00
GGPT-5400k$5.00
HHaiku 4.5200k$0.80
fig. 01 · plays once, replay from the corner

Installation

npx shadcn@latest add "@assistant-ui/elements-data-table"
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 data table lists a handful of rows the model wants in front of you, each one settling in with a short stagger. With a runtime the rows come from a tool's result; standalone you pass them in directly.

Getting started

A data table is the render for a tool call whose result is already row-shaped. Register it once and every matching call gets the same table.

Render the tool call

components/assistant-ui/elements/compare-models-tool-ui.tsx
"use client";

import { useEffect, useState } from "react";
import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
import {
  DataTable,
  type ModelUsage,
} from "@/components/assistant-ui/elements/data-table";

export const CompareModelsToolUI: ToolCallMessagePartComponent<
  { models: string[] },
  readonly ModelUsage[]
> = ({ result, toolCallId }) => {
  const [cycle, setCycle] = useState(0);
  useEffect(() => {
    setCycle((c) => c + 1);
  }, [toolCallId]);

  if (!result) return null;
  return <DataTable rows={result} cycle={cycle} />;
};

Register the tool

app/toolkit.ts
import { defineToolkit } from "@assistant-ui/react";
import { z } from "zod";
import { CompareModelsToolUI } from "@/components/assistant-ui/elements/compare-models-tool-ui";

export const toolkit = defineToolkit({
  compare_models: {
    type: "frontend",
    description: "Compare context window and price across a set of models.",
    parameters: z.object({ models: z.array(z.string()) }),
    execute: async ({ models }) => lookupModelUsage(models),
    render: CompareModelsToolUI,
  },
});
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="data-table">
  <div>{/* header: Model / Context / Cost labels */}</div>
  <div />{/* hairline divider */}
  <div key={cycle}>
    {/* one row per item: initial-letter avatar, name, context, cost */}
  </div>
</div>

The row list is keyed on cycle, so changing it remounts every row and replays the 80ms-per-row stagger from the top. An empty rows array renders only the header, with no placeholder row and no empty-state message.

Examples

Restyle the table

Both lanes take className on the root. Labels, the row avatar, and the numeric columns read from the shared mono and paper tokens in surfaces.tsx, so retheming those tokens restyles every table at once.

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

Comparing fewer models

The header always shows all three columns; rows decides how many data rows appear underneath, down to zero.

<DataTable
  rows={MODEL_USAGE.filter((row) => row.name.startsWith("Sonnet"))}
  cycle={cycle}
/>

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.