Design · Components · Display

Diff Viewer

Code changes as unified or split diffs, line by line, on the kit's field panel.

example.ts+53
1-function greet(name) {
2- console.log("Hello, " + name);
1+function greet(name: string): void {
2+ console.log(`Hello, ${name}!`);
3 }
4
5-greet("World");
5+// Call the function
6+greet("World");
7+greet("TypeScript");

Installation

npx shadcn@latest add @assistant-ui/diff-viewer

The @assistant-ui namespace resolves the Radix or Base UI flavor from your project's style through the style-aware registry entry in components.json. Without that entry, add by direct URL instead:

npx shadcn@latest add https://r.assistant-ui.com/base/diff-viewer.json

Usage

import { DiffViewer } from "@/components/ui/diff-viewer";

// With a unified diff patch
<DiffViewer patch={diffString} />

// With file comparison
<DiffViewer
  oldFile={{ content: "old content", name: "file.txt" }}
  newFile={{ content: "new content", name: "file.txt" }}
/>

As Markdown Language Override

Integrate with MarkdownTextPrimitive to render diff code blocks:

/components/assistant-ui/elements/markdown-text.tsx
import { DiffViewer } from "@/components/ui/diff-viewer";

const MarkdownTextImpl = () => {
  return (
    <MarkdownTextPrimitive
      remarkPlugins={[remarkGfm]}
      className="aui-md"
      components={defaultComponents}
      componentsByLanguage={{
        diff: {
          SyntaxHighlighter: ({ code }) => <DiffViewer patch={code} />
        },
      }}
    />
  );
};

export const MarkdownText = memo(MarkdownTextImpl);

Examples

Unified View

Shows all changes in a single column with +/- indicators. This is the default mode.

example.ts+53
1-function greet(name) {
2- console.log("Hello, " + name);
1+function greet(name: string): void {
2+ console.log(`Hello, ${name}!`);
3 }
4
5-greet("World");
5+// Call the function
6+greet("World");
7+greet("TypeScript");
<DiffViewer patch={diffString} viewMode="unified" />

Split View

Shows old content on the left, new content on the right side-by-side.

example.ts+53
1-function greet(name) {
1+function greet(name: string): void {
2- console.log("Hello, " + name);
2+ console.log(`Hello, ${name}!`);
3 }
3 }
4
4
5-greet("World");
5+// Call the function
6+greet("World");
7+greet("TypeScript");
<DiffViewer patch={diffString} viewMode="split" />

Interactive Mode Toggle

example.ts+53
1-function greet(name) {
2- console.log("Hello, " + name);
1+function greet(name: string): void {
2+ console.log(`Hello, ${name}!`);
3 }
4
5-greet("World");
5+// Call the function
6+greet("World");
7+greet("TypeScript");

Variants

default
example.ts+11
-let x = 1;
+const x = 1;
ghost
example.ts+11
-let x = 1;
+const x = 1;
muted
example.ts+11
-let x = 1;
+const x = 1;

Sizes

sm
example.ts+11
-let x = 1;
+const x = 1;
default
example.ts+11
-let x = 1;
+const x = 1;
lg
example.ts+11
-let x = 1;
+const x = 1;

Theming

Changed lines carry a 2px gutter rule and a faint fill; the diff hue otherwise appears only on the +/- markers and the header stats, while code stays in ink. Override the CSS variables to retheme:

[data-slot="diff-viewer"] {
  --diff-add-bg: color-mix(in oklab, var(--color-green-500) 8%, transparent);
  --diff-add-rule: var(--color-green-500);
  --diff-add-text: var(--color-green-600);
  --diff-add-text-dark: var(--color-green-400);
  --diff-del-bg: color-mix(in oklab, var(--color-red-500) 8%, transparent);
  --diff-del-rule: var(--color-red-500);
  --diff-del-text: var(--color-red-600);
  --diff-del-text-dark: var(--color-red-400);
}
VariableDescription
--diff-add-bgFill for added lines
--diff-add-ruleGutter rule for added lines
--diff-add-textMarker and stat color for additions (light mode)
--diff-add-text-darkMarker and stat color for additions (dark mode)
--diff-del-bgFill for deleted lines
--diff-del-ruleGutter rule for deleted lines
--diff-del-textMarker and stat color for deletions (light mode)
--diff-del-text-darkMarker and stat color for deletions (dark mode)

API Reference

DiffViewer

The main component for rendering diffs.

DiffViewerProps
patch?string

Unified diff string (e.g., output from git diff).

code?string

Alias for patch (for markdown integration).

oldFile?{ content: string; name?: string }

Old file for direct comparison.

newFile?{ content: string; name?: string }

New file for direct comparison.

viewMode"unified" | "split"= "unified"

Display mode for the diff.

variant"default" | "ghost" | "muted"= "default"

Visual style variant.

size"sm" | "default" | "lg"= "default"

Font size.

showLineNumbersboolean= true

Show line numbers.

showIconboolean= false

Show file extension badge in header.

showStatsboolean= true

Show addition/deletion counts in header.

className?string

Additional CSS classes.

Composable API

ComponentDescription
DiffViewerMain component that renders the diff.
DiffViewerFileWrapper for each file in multi-file diffs.
DiffViewerHeaderFile name header with icon and stats.
DiffViewerContentScrollable content area.
DiffViewerLineIndividual line in unified mode.
DiffViewerSplitLineSide-by-side line pair in split mode.
DiffViewerFileBadgeFile extension badge (e.g., "TS").
DiffViewerStatsAddition/deletion count display.

Style Variants (CVA)

ExportDescription
diffViewerVariantsStyles for the root container.
diffLineVariantsBackground styles for diff lines.
diffLineTextVariantsText color styles for diff lines.
import {
  diffViewerVariants,
  diffLineVariants,
  diffLineTextVariants,
} from "@/components/ui/diff-viewer";

// Use variants directly
<div className={diffViewerVariants({ variant: "ghost", size: "sm" })}>
  Custom diff container
</div>

Utilities

ExportDescription
parsePatch(patch)Parse unified diff string into structured data.
computeDiff(old, new)Compute diff between two strings.
ParsedLineType for a single diff line.
ParsedFileType for a parsed file with lines and stats.
SplitLinePairType for a side-by-side line pair.

Styling

Data Attributes

Use data attributes for custom styling:

AttributeValuesDescription
data-slot"diff-viewer", "diff-viewer-header", "diff-viewer-line", etc.Component identification
data-type"add", "del", "normal", "empty"Line type
data-view-mode"unified", "split"Current view mode
data-variant"default", "ghost", "muted"Current variant

Custom CSS Example

[data-slot="diff-viewer"][data-view-mode="split"] {
  /* Custom split view styles */
}

[data-slot="diff-viewer-line"][data-type="add"] {
  /* Custom addition styles */
}

[data-slot="diff-viewer-line"][data-type="del"] {
  /* Custom deletion styles */
}