Diff Viewer

Render code diffs with syntax highlighting for additions and deletions.

This is a standalone component that does not depend on the assistant-ui runtime.

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

Installation

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

Usage

import { DiffViewer } from "@/components/assistant-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/markdown-text.tsx
import { DiffViewer } from "@/components/assistant-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.

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

Split View

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

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

Interactive Mode Toggle

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

Variants

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

Sizes

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

Theming

DiffViewer uses CSS variables for colors. Override them in your CSS:

[data-slot="diff-viewer"] {
  --diff-add-bg: rgba(46, 160, 67, 0.15);
  --diff-add-text: #1a7f37;
  --diff-add-text-dark: #3fb950;
  --diff-del-bg: rgba(248, 81, 73, 0.15);
  --diff-del-text: #cf222e;
  --diff-del-text-dark: #f85149;
}
VariableDescription
--diff-add-bgBackground for added lines
--diff-add-textText color for added lines (light mode)
--diff-add-text-darkText color for added lines (dark mode)
--diff-del-bgBackground for deleted lines
--diff-del-textText color for deleted lines (light mode)
--diff-del-text-darkText color for deleted lines (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.

showLineNumbers: boolean= true

Show line numbers.

showIcon: boolean= true

Show file extension badge in header.

showStats: boolean= 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/assistant-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 */
}