Elements

Code diff

A unified diff with tinted additions and removals, sized for chat.

composer.tsx+2 1
export function Composer() {
const threadId = useThreadId();
const composer = useComposer();
const [draft, setDraft] = useState("");
+ const draft = useDraft(threadId);
+ useEffect(() => hydrate(draft), [threadId]);
return (
<form onSubmit={composer.send}>
{/* … */}
fig. 01 · plays once, replay from the corner

Installation

npx shadcn@latest add "@assistant-ui/elements-code-diff"
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 code diff shows one file's changed lines: a filename and its net additions and deletions up top, then every context, added, and removed line beneath, each tinted and gutter-marked by kind. With a runtime the filename and lines come from an edit tool's result; standalone you supply the same shape directly.

Getting started

Render the tool call

components/assistant-ui/elements/edit-file-tool-ui.tsx
"use client";

import type { ToolCallMessagePartComponent } from "@assistant-ui/react";
import { CodeDiff, type DiffLine } from "@/components/assistant-ui/elements/code-diff";

type EditFileArgs = { path: string };
type EditFileResult = { additions: number; deletions: number; lines: DiffLine[] };

export const EditFileToolUI: ToolCallMessagePartComponent<
  EditFileArgs,
  EditFileResult
> = ({ args, result }) => {
  if (!result) return null;
  return (
    <CodeDiff
      filename={args.path}
      additions={result.additions}
      deletions={result.deletions}
      lines={result.lines}
      cycle={0}
    />
  );
};

Register the tool

app/toolkit.ts
import { defineToolkit } from "@assistant-ui/react";
import { EditFileToolUI } from "@/components/assistant-ui/elements/edit-file-tool-ui";

export const toolkit = defineToolkit({
  edit_file: {
    type: "backend",
    render: EditFileToolUI,
  },
});
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>
  );
}

The diff renders once the call completes; while it runs, result is undefined and the renderer above returns null. See Tool UI for a loading state in between.

Anatomy

<div data-slot="code-diff">
  <div>{/* filename, +additions and −deletions counts */}</div>
  <div>{/* horizontally scrollable line list, colored and gutter-marked by kind */}</div>
</div>

The additions and deletions counts always render, even at zero; there is no threshold that hides a +0 or −0. Lines keep their own whitespace and scroll horizontally inside their own container rather than wrapping, so a long line never pushes the card wider than its max-w-md. Each line's entrance is staggered by 60ms per row and keyed on cycle, so bumping cycle replays the stagger for identical line text.

Examples

Restyle the diff

Both lanes take className on the root. The card surface comes from the shared paper token and the scroll region from codeScroll/codeSurface, both in surfaces.tsx.

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

No lines changed

An empty lines array is a valid diff: the header still shows the filename and counts, and the body renders as an empty scroll region.

<CodeDiff filename="README.md" additions={0} deletions={0} lines={[]} cycle={0} />

API reference

Tool-call render props

PropTypeDescription
args{ path: string }The file the model asked to edit.
result{ additions: number; deletions: number; lines: DiffLine[] } | undefinedThe applied patch, once the call completes.

See Tool UI for the full render-prop surface and for backend tool registration.