caliper uidocs

File diff

Free

A line-by-line comparison of two versions of a file.

Installs components/ui/file-diff.tsxDepends on @radix-ui/react-slot, lucide-reactMotion none in component source
pnpm dlx shadcn@latest add https://caliperui.site/r/file-diff.json
+7 −5
7 added, 5 removed in src/counter.tsx
import { useState } from "react";
Removed: export function Counter({ start = 0 }: { start?: number }) {
Added: export function Counter({ start = 0, step = 1 }: { start?: number; step?: number }) {
const [count, setCount] = useState(start);
Removed: const label = "Count: " + count;
Added: const label = `Count: ${count}`;
function increment() {
Removed: setCount(count + 1);
Added: setCount((current) => Math.min(current + step, maxCount));
}
return (
<div className="counter">
Removed: <p>{label}</p>
Removed: <button onClick={increment}>Add</button>
Added: <p aria-live="polite">{label}</p>
Added: <button onClick={increment} disabled={count >= maxCount}>
Added: Add
Added: </button>
</div>
);
}

When to use #

Use it for

  • Showing what an agent or a commit changed in a file.
  • Reviewing edits before they are applied.

Skip it when

  • The comparison is of rich text or images.
  • Files are very large; page or collapse them first.

Installation #

terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/file-diff.json

Adds its 5 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.

Usage #

This is the code behind the preview above. It is built from FileDiff and diffLines.

example.tsx
import { useState } from "react"
import { Button } from "@/components/ui/button"
import { FileDiff } from "@/components/ui/file-diff"

const before = `import { useState } from "react";

export function Counter({ start = 0 }: { start?: number }) {
  const [count, setCount] = useState(start);
  const label = "Count: " + count;

  function increment() {
    setCount(count + 1);
  }

  return (
    <div className="counter">
      <p>{label}</p>
      <button onClick={increment}>Add</button>
    </div>
  );
}

export const defaultStart = 0;
export const maxCount = 99;
`;

const after = `import { useState } from "react";

export function Counter({ start = 0, step = 1 }: { start?: number; step?: number }) {
  const [count, setCount] = useState(start);
  const label = \`Count: \${count}\`;

  function increment() {
    setCount((current) => Math.min(current + step, maxCount));
  }

  return (
    <div className="counter">
      <p aria-live="polite">{label}</p>
      <button onClick={increment} disabled={count >= maxCount}>
        Add
      </button>
    </div>
  );
}

export const defaultStart = 0;
export const maxCount = 99;
`;

export function FileDiffDemo() {
  const [, setDecision] = useState("");
  return (
    <FileDiff
            path="src/counter.tsx"
            before={before}
            after={after}
            actions={
              <>
                <Button variant="secondary" size="sm" onClick={() => setDecision("Rejected src/counter.tsx")}>Reject</Button>
                <Button size="sm" onClick={() => setDecision("Accepted src/counter.tsx")}>Accept</Button>
              </>
            }
          />
  );
}

API reference #

FileDiff

component
PropTypeDefaultDescription
path *string—
before *string—
after *string—
languageCodeLanguage—
contextnumber3Unchanged lines kept around each change.
view"unified" | "split""unified"
actionsReactNode—Buttons for the header, such as Accept and Reject.
defaultOpenbooleantrue
classNamestring—

diffLines

component

No props of its own; it accepts the native props of its root element.

Accessibility #

It exposes group and region semantics, with aria-label, aria-expanded, aria-controls, aria-hidden and aria-pressed kept in step with state.

No custom key handling; Tab and the native activation keys do the work.

Motion #

It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.