Diff Viewer
Show the difference between two versions of a file, unified or side by side, with what changed inside each line marked.
import { DiffViewer } from '@youj/react'
Installation & usage
npm i @youj/reactFor an app already set up with npx @youj/cli init, which installs the peers (@youj/core, @youj/motion, motion and Base UI) and wires the provider. New app? Start at Installation.
Use a diff viewer wherever someone reviews a change to code or config: a pull request, a deploy's changes, a config history, an AI edit waiting for approval. For a snippet that isn't a change, use Code Block.
import { DiffViewer } from '@youj/react'
export function ConfigChange({ previous, current }: { previous: string; current: string }) {
return <DiffViewer before={previous} after={current} filename="config/app.json" language="json" />
}- Two versions or a patch.
beforeandafterare compared line by line. Line endings don't count (CRLF reads as LF), but a missing newline at the end of the file does, as in git. Or passpatch, a unified diff; only its first file is shown, and the lines between its hunks can't be opened because the patch doesn't carry them. - Highlighting is CodeBlock's built-in tokenizer, so
languagetakes the same values (ts,tsx,json,yaml,css,py,go…). It produces tokens, never HTML. - Unchanged lines fold beyond
contextlines on each side of a change (3 by default) into a row that opens in place.context={Infinity}shows every line. - View and wrap are
view/defaultView/onViewChangeandwrap/defaultWrap/onWrapChange, so an app can remember them. - Copy copies
after, or the patch when you passed one.
Description
A CI workflow moved to a newer runner and Node, and run on pull requests too.
Pass before and after and the diff is computed in the component (Myers' algorithm, line by line). Inside each pair of removed and added lines, the words that changed take a stronger tint. Press J and K (or Alt+↓ and Alt+↑) anywhere in the diff to step through the changes.
One file of a pull request, side by side: before on one side, after on the other, each removal paired with its addition.
The split view always wraps long lines, so both sides stay in view. In the unified view, the wrap button chooses between wrapping and scrolling sideways.
A unified diff as git diff prints it. Each hunk opens on its @@ header, and the file name comes from the patch.
Code
diff-viewer-demo.tsx, the Demo story. The component itself comes from the package.
Reference
Propsbefore, after, patch, language, filename, view, wrap, context…
Props
| Prop | Type | |
|---|---|---|
before | stringThe old version of the file. With | – |
after | stringThe new version of the file. | – |
patch | stringA unified diff ( | – |
language | stringLanguage for highlighting, as CodeBlock takes it: default '' | '' |
filename | stringThe file's path for the header. A patch names its own when this is left out. | – |
view | DiffView'unified''split'
| – |
defaultView | DiffView'unified''split'The view at first, uncontrolled. default 'unified' | 'unified' |
onViewChange | (view: DiffView) => void | – |
wrap | booleanWrap long lines instead of scrolling sideways, controlled. The split view always wraps. | – |
defaultWrap | booleanWrap at first, uncontrolled. default false | false |
onWrapChange | (wrap: boolean) => void | – |
context | numberUnchanged lines kept on each side of a change; longer runs fold. default 3 | 3 |
sound | booleanWith sound on ( default true | true |
Other props pass through: extends ComponentProps<'figure'>. | ||
Data attributes
The root carries data-yj-view (unified or split) and data-yj-wrap when lines wrap. Each line's code cell carries data-yj-line (add, del or context), and the row of the change you stepped to carries data-yj-current.
MotionStepping, view switch, copy · reduced motion
Motion
| Moment | Tokens | ||
|---|---|---|---|
| Step to a changethe change glides to the middle of the view and an accent bar marks its first line | no token smooth scroll | smooth scroll | |
| View switchthe segmented control’s indicator glides to the pressed view | spring.smooth0.36s, bounce 0 | 0.36s, bounce 0 | |
| Copiedthe copy icon’s check, then back to rest after a beat | no token 3 × deliberate | 3 × deliberate |
Reduced motion
Stepping jumps to the change, the view indicator moves at once and the copy icon swaps to its check.
Reduced motion
Every state shows at once; the diff reads the same.
AccessibilityTable, keyboard, announcements
Accessibility
- The viewer is a
figurenamed by the file name, and the diff is a table whose caption names the file and counts the additions and deletions. - Each changed line reads its change and number first: "Added line 12: …", "Removed line 9: …". Line numbers and the +/− signs are decorative and can't be selected, so copying by hand takes only the code. In the split view, the columns are headed Before and After.
- The diff is a focusable region. J and K, or Alt+↓ and Alt+↑, step to the next and previous change (described on the region, and as
aria-keyshortcuts); the header's position, "Change 3 of 7", is announced politely. Previous and Next change are buttons too. - A folded run is a button, "12 unchanged lines"; opening it keeps focus in the diff.
- The view is a segmented control and wrap a toggle button, each named. Copy announces its result, and a refused clipboard says so.
- On a right-to-left page the header follows the page while the code stays left to right, as code does.