Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Text

Pro

Diff Text for React

A diff that plays out line by line: removed lines struck through, added lines sliding in. For React with Base UI, when a changelog or a code sample should show what changed.

Framework

Diff Text shows a change the way a pull request does, and plays it: removed lines tint red and are struck through, then added lines slide in tinted green, one after the other, while the unchanged lines around them stay put. You write it as a unified diff, the lines git diff prints, starting with +, - or a space.

Use it for a changelog, a migration guide or a hero that shows one change in a few lines. For a code sample that comes in token by token, use Syntax Text; for a data object, JSON Text. Each changed line runs a CSS keyframe animation the install adds to your stylesheet, delayed by its place among the changes, once the diff scrolls into view, with no animation library. With reduced motion the diff is simply there.

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Usage

import { DiffText } from "@/components/ui/diff-text";
<DiffText
  text={`
 const cache = createCache();
-cache.set(key, value);
+cache.set(key, value, { ttl: 60 });
`}
/>

Demos

Prose

Diff Text isn't only for code: a copy edit or a release note reads the same way. The diff takes the font you give it, so font-sans sets it as prose; long lines wrap under their text, never under the + or -.

Preview width

Timing

delay is the wait before the first change, 200 ms by default; stagger the time between one changed line and the next, 200 ms; duration how long one line takes to change, 500 ms. Unchanged lines take no time, so the changes follow each other however far apart they are.

Preview width

Accessibility

Removed lines are real <del> and added lines real <ins>, each with a visually hidden "Removed:" or "Added:" before it, so screen readers say what changed rather than relying on red and green; the + and - are hidden from them. Pass removedLabel and addedLabel in your page's language. The diff always reads left to right, even on a right-to-left page, as code does. With reduced motion every line is in its final state from the start.

FAQ

How do I write the diff?

One line per line, as git diff prints a hunk: + before an added line, - before a removed one, a space before an unchanged one. A blank line at the very start or end is dropped, so a template literal can open and close on its own lines. Leave out the diff --git, ---, +++ and @@ headers.

Where do the colours come from?

Your theme: removed lines use --destructive, added lines --success and --success-foreground, which the install adds to your theme if it doesn't have them. They're tints, so the text stays readable in light and dark.

When does it play?

Once, when a quarter of the diff is in view. Pass trigger="mount" to play it as soon as the page shows it. A page without JavaScript shows the finished diff.

Can I show a long diff?

Yes, but it reads best at five to fifteen lines. The time grows with the number of changes, not lines; for many changes, lower stagger.

What do I get if I copy it?

The lines' text: the + and - and the hidden labels can't be selected, so they stay out of what you copy.

Can it play again?

Give it a new key and React renders it fresh, so it plays again from the start.

What happens with reduced motion?

Nothing moves: removed lines are struck through and added lines are in place from the start.

API Reference

DiffText

<div> data-slot="diff-text"

PropType
textstring

The diff, a line per line as in a unified diff: + starts an added line, - a removed one, a space (or anything else) an unchanged one.

addedLabel?string

What screen readers say before an added line. @default "Added".

Default
"Added"
delay?number

The wait before the first change, in milliseconds. @default 200.

duration?number

How long one line takes to change, in milliseconds. @default 500.

removedLabel?string

What screen readers say before a removed line. @default "Removed".

Default
"Removed"
stagger?number

The time between one changed line and the next, in milliseconds. @default 200.

trigger?"mount" | "view"

When it plays: as soon as it's on the page, or once it scrolls into view.

Type
"mount" | "view"
Default
"view"
render?ReactElement | ((props: object, state: object) => ReactElement)

Replace the host element or compose it with another component.

Type
ReactElement | ((props: object, state: object) => ReactElement)
className?string

Merged with the part's classes.

AttributeType
data-change?string

Mirrors change.

data-view?string

"in" when visible is true, otherwise "out".

CSS variableType
--diff-text-delaystring

The wait before the first change (200ms).

--diff-text-durationstring

One line's change (500ms).

--diff-text-staggerstring

The time between one changed line and the next (200ms).

All 97 text effects