Diff
Line-level diff viewer with both unified and side-by-side (split) layouts. The diff is computed with a built-in LCS algorithm — no dependencies — and exposed as render parts you style yourself.
Features
Example
Anatomy
import { Diff } from '@wire-ui/react'
<Diff.Root oldValue={before} newValue={after}>
<Diff.Stats>
{({ additions, deletions }) => (
<div>+{additions} −{deletions}</div>
)}
</Diff.Stats>
<Diff.Unified>
{({ line }) => (
<div>{line.oldLine} {line.newLine} {line.content}</div>
)}
</Diff.Unified>
</Diff.Root>Split layout
Diff.Split pairs deletes and inserts into side-by-side rows. Each row provides an optional left (old) and right (new) DiffLine — either may be absent when one side has no counterpart.
<Diff.Split>
{({ left, right }) => (
<div className="grid grid-cols-2 divide-x divide-[#e5e7eb]">
<div className={left ? lineClass(left.type) : 'bg-[#fafafa]'}>
{left?.oldLine} {left?.content}
</div>
<div className={right ? lineClass(right.type) : 'bg-[#fafafa]'}>
{right?.newLine} {right?.content}
</div>
</div>
)}
</Diff.Split>Styling
Diff ships no styles. Diff.Unified wraps each line in a data-diff-line element carrying data-type, and Diff.Split wraps each row in a data-diff-row element carrying data-left / data-right. Style the line/row wrappers — or branch on line.type inside the render function — to color insertions and deletions.
[data-diff-line][data-type="insert"] { background: #f0fdf4; }
[data-diff-line][data-type="delete"] { background: #fef2f2; }Using data attributes
Diff.Unified renders a <div data-diff-view="unified"> whose children carry data-diff-line and data-type. Diff.Split renders a <div data-diff-view="split"> whose children carry data-diff-row, data-left, and data-right.
[data-diff-view="split"] [data-left="delete"] { background: #fef2f2; }
[data-diff-view="split"] [data-right="insert"] { background: #f0fdf4; }Root props
| Prop | Type | Default | Description |
|---|---|---|---|
oldValue | string | required | The original (“before”) text |
newValue | string | required | The updated (“after”) text |
Root also accepts all standard div attributes (className, id, …).
Unified props
| Prop | Type | Description |
|---|---|---|
children | (props: { line: DiffLine }) => ReactNode | Render function for a single unified line |
Split props
| Prop | Type | Description |
|---|---|---|
children | (props: DiffRow) => ReactNode | Render function for a single side-by-side row |
Stats props
| Prop | Type | Description |
|---|---|---|
children | (props: DiffStats) => ReactNode | Render function for the additions/deletions summary |
Types
type DiffLineType = 'equal' | 'insert' | 'delete'
interface DiffLine {
type: DiffLineType
content: string // line text, no trailing newline
oldLine?: number // 1-based line in the old text (absent for inserts)
newLine?: number // 1-based line in the new text (absent for deletes)
}
interface DiffRow {
left?: DiffLine // old side
right?: DiffLine // new side
}
interface DiffStats {
additions: number
deletions: number
}Data attributes
| Attribute | Element | Values |
|---|---|---|
data-diff-view | Unified / Split container | "unified" / "split" |
data-diff-line | Unified line wrapper | Present on every line |
data-type | Unified line wrapper | "equal" / "insert" / "delete" |
data-diff-row | Split row wrapper | Present on every row |
data-left | Split row wrapper | Left line type, if present |
data-right | Split row wrapper | Right line type, if present |
Accessibility
Diff renders plain div elements with no interactive behavior — it is a presentational viewer. Provide your own semantics (for example, a caption naming the file being compared) when embedding it in a larger UI.