Diff Apply

Animated Code Diff for React

A code change that applies itself in place. Removed lines strike through and close up, new lines open in tinted, and the line numbers below roll to their new values. Each change then waits for Keep or Undo, and Undo plays it backwards.

  • Code
  • Developer
  • AI

Uses motion, clsx, tailwind-merge

Open in ChatGPTOpen in Claude
stagger0.46s(replays the demo)
duration0.32s(replays the demo)
<DiffApply />

replays the demo when you let go.

Retry invoice fetches when Northwind returns a 5xx, with backoff

billing/fetch-invoice.ts
import type { Invoice } from "./types";

const API = "https://api.northwind.dev/v2";

export async function fetchInvoice(id: string): Promise<Invoice> {
  const res = await fetch(`${API}/invoices/${id}`);
  if (!res.ok) {
    throw new Error("Request failed");
  }
  return res.json();
}

export function formatTotal(invoice: Invoice) {
  return new Intl.NumberFormat("en-US", {
    style: "currency",
    currency: invoice.currency,
  }).format(invoice.total / 100);
}
Take this demo to your coding agent.
Add this moment from KeyframeUI to our project and adapt it to our content.

Diff Apply: A code change that applies itself in place. Removed lines strike through and close up, new lines open in tinted, and the line numbers below roll to their new values. Each change then waits for Keep or Undo, and Undo plays it backwards.
This demo, Agent Refactor: An assistant wraps an invoice fetch in a retry with backoff: an import, a doc comment and the new body land one after another. Keep or undo each change, or all of them from the header.

1. Install it:
   npx shadcn@latest add @keyframeui/diff-apply-demo
   (If the CLI can't find @keyframeui, use https://keyframeui.com/r/diff-apply-demo.json instead.)
   It adds the demo, components/diff-apply-demo.tsx, and the component, components/ui/diff-apply.tsx.

2. Make it ours: our copy, data, images and colors. Keep the structure the motion depends on. If we only need the effect, use <DiffApply> on its own.

3. These props set its feel. Change them before you change the file:
   - stagger (default 0.46): Seconds between one change starting and the next. Files with many changes tighten it, so the whole run stays short.
   - duration (default 0.32): Seconds removed lines take to close and new lines to open, on one spring. Undo plays back a little quicker.

4. Don't change, in components/ui/diff-apply.tsx:
   - The box reserves the taller version's height (the summed `data-ver` rows) and follows each change by scrolling only itself, so nothing on the page moves while the change plays.
   - Removed lines close and new lines open on one height spring, so the code below slides as one block; Undo plays it back on the quicker `undo` spring, scaled to `duration`.
   - Each timeline step only moves a hunk on from the status it expects, so a Keep or Undo mid-run wins over the timers still pending.
   - The strike is a transparent copy of the line showing only its line-through, revealed left to right by `clipPath`, so wrapped lines strike too.
   - Reduced motion (read only after hydration) drops the band, strike, fold, roll and scroll: the finished diff appears at once, tinted.
   Its timing is all in the TIMING block at the top of that file.

Docs: https://keyframeui.com/components/diff-apply.md

Installation

Add the component on its own with the CLI, or copy the file in by hand.

The CLI writes components/ui/diff-apply.tsx and installs motion, clsx and tailwind-merge. It needs the @keyframeui registry in components.json; add it once per project.

npx shadcn@latest add @keyframeui/diff-apply

Examples

Finished sections built with Diff Apply. Each one installs whole, with the component included.

Codemod Upgrade

A framework upgrade rewrites a config file in four small changes. The box is capped with `maxHeight`, so it scrolls each change into view as it lands.

$npx harbor upgrade 4
harbor.config.ts
import { defineConfig } from "harbor/config";
import tailwind from "@harbor/tailwind";
import { sentry } from "@harbor/sentry";

export default defineConfig({
  name: "field-and-fold",
  target: "node16",
  experimental: {
    edge: true,
    turboBuild: true,
  },
  images: {
    domains: ["images.unsplash.com", "cdn.fieldandfold.co"],
  },
  plugins: [
    tailwind(),
    sentry({ dsn: process.env.SENTRY_DSN }),
  ],
  headers: async () => [
    { source: "/(.*)", headers: [{ key: "X-Frame-Options", value: "DENY" }] },
  ],
  legacyBrowsers: true,
});
Take this demo to your coding agent.
Add this moment from KeyframeUI to our project and adapt it to our content.

Diff Apply: A code change that applies itself in place. Removed lines strike through and close up, new lines open in tinted, and the line numbers below roll to their new values. Each change then waits for Keep or Undo, and Undo plays it backwards.
This demo, Codemod Upgrade: A framework upgrade rewrites a config file in four small changes. The box is capped with `maxHeight`, so it scrolls each change into view as it lands.

1. Install it:
   npx shadcn@latest add @keyframeui/diff-apply-migration-demo
   (If the CLI can't find @keyframeui, use https://keyframeui.com/r/diff-apply-migration-demo.json instead.)
   It adds the demo, components/diff-apply-migration-demo.tsx, and the component, components/ui/diff-apply.tsx.

2. Make it ours: our copy, data, images and colors. Keep the structure the motion depends on. If we only need the effect, use <DiffApply> on its own.

3. These props set its feel. Change them before you change the file:
   - stagger (default 0.46): Seconds between one change starting and the next. Files with many changes tighten it, so the whole run stays short.
   - duration (default 0.32): Seconds removed lines take to close and new lines to open, on one spring. Undo plays back a little quicker.

4. Don't change, in components/ui/diff-apply.tsx:
   - The box reserves the taller version's height (the summed `data-ver` rows) and follows each change by scrolling only itself, so nothing on the page moves while the change plays.
   - Removed lines close and new lines open on one height spring, so the code below slides as one block; Undo plays it back on the quicker `undo` spring, scaled to `duration`.
   - Each timeline step only moves a hunk on from the status it expects, so a Keep or Undo mid-run wins over the timers still pending.
   - The strike is a transparent copy of the line showing only its line-through, revealed left to right by `clipPath`, so wrapped lines strike too.
   - Reduced motion (read only after hydration) drops the band, strike, fold, roll and scroll: the finished diff appears at once, tinted.
   Its timing is all in the TIMING block at the top of that file.

Docs: https://keyframeui.com/components/diff-apply.md

Release Note Edit

Not only code: a writing assistant tightens a Markdown release note, with `wrap` on and no line numbers. The reply under it follows `onComplete` and `onReview`.

Tighten this for the changelog. Be specific.
release-notes.md
## Tinta 2.4

We are really excited to announce that Tinta 2.4 is finally here and available for everyone to use today!

### What's new

- Exports are now a lot faster than they used to be.
- Fixed a bug where some people could sometimes not sign in.
- Dark mode.

Thanks to everyone who sent us feedback!

Cut 12 words and named the numbers. Keep what reads right.

Take this demo to your coding agent.
Add this moment from KeyframeUI to our project and adapt it to our content.

Diff Apply: A code change that applies itself in place. Removed lines strike through and close up, new lines open in tinted, and the line numbers below roll to their new values. Each change then waits for Keep or Undo, and Undo plays it backwards.
This demo, Release Note Edit: Not only code: a writing assistant tightens a Markdown release note, with `wrap` on and no line numbers. The reply under it follows `onComplete` and `onReview`.

1. Install it:
   npx shadcn@latest add @keyframeui/diff-apply-copy-demo
   (If the CLI can't find @keyframeui, use https://keyframeui.com/r/diff-apply-copy-demo.json instead.)
   It adds the demo, components/diff-apply-copy-demo.tsx, and the component, components/ui/diff-apply.tsx.

2. Make it ours: our copy, data, images and colors. Keep the structure the motion depends on. If we only need the effect, use <DiffApply> on its own.

3. These props set its feel. Change them before you change the file:
   - stagger (default 0.46): Seconds between one change starting and the next. Files with many changes tighten it, so the whole run stays short.
   - duration (default 0.32): Seconds removed lines take to close and new lines to open, on one spring. Undo plays back a little quicker.

4. Don't change, in components/ui/diff-apply.tsx:
   - The box reserves the taller version's height (the summed `data-ver` rows) and follows each change by scrolling only itself, so nothing on the page moves while the change plays.
   - Removed lines close and new lines open on one height spring, so the code below slides as one block; Undo plays it back on the quicker `undo` spring, scaled to `duration`.
   - Each timeline step only moves a hunk on from the status it expects, so a Keep or Undo mid-run wins over the timers still pending.
   - The strike is a transparent copy of the line showing only its line-through, revealed left to right by `clipPath`, so wrapped lines strike too.
   - Reduced motion (read only after hydration) drops the band, strike, fold, roll and scroll: the finished diff appears at once, tinted.
   Its timing is all in the TIMING block at the top of that file.

Docs: https://keyframeui.com/components/diff-apply.md

Props

before Required

Typestring

The file as it was. A leading blank line and trailing whitespace are trimmed, so template literals read naturally.

after Required

Typestring

The file after the edit. The component works out the changed lines itself (a line diff) and groups them into changes that play one after another.

filename

Typestring

Shown in the tab. Its extension picks the highlighting when language isn't set.

language

Typestring

Highlighting: ts/js (and tsx, jsx), json, css or md. Anything else is plain. Defaults to the filename's extension.

review

TypebooleanDefaulttrue

After a change lands, wait for Keep or Undo: a small pill at the change's right edge, plus Undo all and Keep all in the header, usable while later changes still play. Off, the tint fades 1.4s after each change lands and the file reads as finished.

play

Typeboolean

Leave unset to play once the block is 40% in view. Set it to drive the block yourself: true plays the change, false puts the file back to before.

replay

TypebooleanDefaulttrue

Show a Replay button in the header once the change has played. It fades the file back to before and plays it again.

wrap

TypebooleanDefaultfalse

Wrap long lines instead of scrolling sideways. Use it for prose; the strike follows every wrapped line.

lineNumbers

TypebooleanDefaulttrue

Show the line-number gutter, with + and − beside changed lines. It stays put while the code scrolls sideways.

maxHeight

Typenumber | string

Cap the code box's height, in pixels or any CSS length. The box scrolls inside, and follows each change into view without scrolling the page.

stagger

TypenumberDefault0.46

Seconds between one change starting and the next. Files with many changes tighten it, so the whole run stays short.

duration

TypenumberDefault0.32

Seconds removed lines take to close and new lines to open, on one spring. Undo plays back a little quicker.

onComplete

Type() => void

Called once every change has landed.

onReview

Type(decisions: DiffApplyDecision[]) => void

Called after each Keep or Undo with the decision for every change, in order: "pending", "kept" or "undone".

className

Typestring

Classes for the card, e.g. a max width or radius. Colors come from the theme's background, foreground, muted and border; the highlighter's hues are one map at the top of the file.