Skip to content
omaris

Data Display

DiffViewer

A diff, unified or side by side, with the syntax colouring the rest of the library uses.

import { DiffViewer } from 'omaris'
Learn

Examples

Basic

Give it before and after. A second diff runs inside a changed pair of lines, so a renamed variable shows as one word moving rather than two whole lines replaced.

total.ts +3 −3
export function total(items) {
export function total(items, tax = 0) {
let sum = 0;
for (const item of items) {
sum += item.price;
sum += item.price * item.qty;
}
return sum;
return sum * (1 + tax);
}
<script lang="ts">	import { DiffViewer } from 'omaris';​	const before = `export function total(items) {	let sum = 0;	for (const item of items) {		sum += item.price;	}	return sum;}`;​	const after = `export function total(items, tax = 0) {	let sum = 0;	for (const item of items) {		sum += item.price * item.qty;	}	return sum * (1 + tax);}`;</script>​<DiffViewer {before} {after} lang="ts" title="total.ts" class="w-full" />

Split and context

switchable offers the unified / split toggle, and view is bindable so the choice is yours to keep. context is how many unchanged lines survive either side of a change — the rest collapse into a strip you can click open.

tokens.ts (main) tokens.ts (branch) +3 −2
// boilerplate line 4
// boilerplate line 4
const theme = 'light';
const theme = 'system';
const radius = 8;
const radius = 12;
const easing = 'emphasized';
// trailing line 1
// trailing line 1
// trailing line 2
// trailing line 2
// trailing line 3
// trailing line 3
// trailing line 4
// trailing line 4
view: split
<script lang="ts">	import { DiffViewer, Text, type DiffView } from 'omaris';​	const before = `// boilerplate line 1// boilerplate line 2// boilerplate line 3// boilerplate line 4const theme = 'light';const radius = 8;// trailing line 1// trailing line 2// trailing line 3// trailing line 4`;​	const after = `// boilerplate line 1// boilerplate line 2// boilerplate line 3// boilerplate line 4const theme = 'system';const radius = 12;const easing = 'emphasized';// trailing line 1// trailing line 2// trailing line 3// trailing line 4`;​	let view = $state<DiffView>('split');</script>​<div class="flex w-full flex-col gap-2">	<DiffViewer		{before}		{after}		lang="ts"		bind:view		switchable		context={1}		titles={['tokens.ts (main)', 'tokens.ts (branch)']}		height="20rem"	/>	<Text variant="label-sm" tone="muted">view: {view}</Text></div>

From a patch

Hand it the patch that git diff printed and it renders that instead — identically, since both end up in the same shape. wrap keeps long lines on screen, and classes.viewport replaces the height the property sets.

+3 −1
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
import { extendTailwindMerge } from 'tailwind-merge';
const twMerge = extendTailwindMerge({ extend: { classGroups: { shadow: ['shadow-1'] } } });
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
<script lang="ts">	import { DiffViewer } from 'omaris';​	const patch = `diff --git a/src/lib/utils.ts b/src/lib/utils.ts--- a/src/lib/utils.ts+++ b/src/lib/utils.ts@@ -1,7 +1,8 @@ import { clsx, type ClassValue } from 'clsx';-import { twMerge } from 'tailwind-merge';+import { extendTailwindMerge } from 'tailwind-merge';++const twMerge = extendTailwindMerge({ extend: { classGroups: { shadow: ['shadow-1'] } } });​ export function cn(...inputs: ClassValue[]) { 	return twMerge(clsx(inputs)); }`;</script>​<DiffViewer	{patch}	lang="ts"	size="sm"	wrap	view="unified"	height="auto"	classes={{ viewport: 'max-h-64', header: 'bg-surface-container-low' }}	class="w-full"/>

When to use it

Use it for

  • Two versions of a file or setting: a review, an audit trail, what changed since the last publish. Pass before and after. A renamed variable shows as one word moving, not two lines replaced.
  • A patch from a server or git diff. patch renders it as is, with no before/after to reconstruct.
  • A long file with one change. context={3} keeps three lines either side and folds the rest into a strip that opens on click.
  • Side by side on a wide screen. view="split", plus switchable to let the reader choose. view is bindable, so the choice can be kept.

Not for

  • A hand-written five-line change in a docs page → Code Block with diff.
  • Reviewing and fixing the change → Code Editor beside it. The viewer is read-only.
  • Two JSON documents → JSON Viewer to explore one. For the difference, pretty-print both with the same key order and pass them here as lang="json".
  • Prose that changed. A line diff of a paragraph is one changed line. Diff at the word level in the editor that owns the prose.

Do

  • Set lang. Both sides are highlighted whole, so a block comment or a multi-line string stays coloured across a change.
  • Name the file with title, or titles={['old.ts', 'new.ts']} for a rename. Keep stats so +12 −4 says how big the change is.
  • Cap it with height in a sheet or card. Use wrap in a narrow column instead of sideways scrolling.
  • Set emptyText for "nothing changed". A blank frame reads as broken.

Don't

  • Pass context={Infinity} for a long file. 400 unchanged lines are not the change.
  • Force split on a phone. Unified is the one that fits.
  • Re-tint the rows by hand. The three change colours are slots shared by both views.

Quick reference

size
  • sm
  • md (default)

API

DiffViewer

A diff, unified or side by side, with the syntax colouring the rest of the library uses.

Two things separate this from a red-and-green list of lines. It runs a second diff inside a changed pair of lines, so a renamed variable shows as one word moving rather than two whole lines replaced; and long runs of untouched code collapse to a strip you can click, because the point of a diff is what changed and 400 unchanged lines are not it.

Feed it before and after and it does the work, or hand it the patch that git diff printed and it will render that instead — identically, since both end up in the same shape.

import { DiffViewer } from 'omaris'
<DiffViewer lang="ts" {before} {after} view="split" context={3} />

Parts

Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.

root
No description in the source yet.
header
Filename, stats, and the view switch.
title
No description in the source yet.
stats
+12 −4.
added
No description in the source yet.
removed
No description in the source yet.
action
No description in the source yet.
viewport
The scroll container.
table
Everything inside the scroller, sized to the widest line.
row
One line, or one pair of lines in split view.
side
Half a split row.
gutter
A line number, pinned against horizontal scroll.
sign
The + / − column.
code
The code itself.
word
A run of characters added or removed inside a changed line.
gap
The "N unchanged lines" strip.
empty
Shown when the two sides are the same.

Props

before

Defaults to ''

string

The old text. Ignored when patch is given.

after

Defaults to ''

string

The new text.

patch
string

A unified patch, as git diff prints it. Wins over before/after.

lang

Defaults to 'plain'

string

ts, svelte, css, … — the same names CodeBlock takes.

view bindable

Defaults to 'unified'

DiffView
size

Defaults to 'md'

DiffViewerSize
sm
md
context

Defaults to 3

number

Unchanged lines kept either side of a change. Infinity keeps them all.

lineNumbers

Defaults to true

boolean
wrap

Defaults to false

boolean
title
string

Filename in the header.

titles
[string, string]

Per-side filenames, for a rename. Falls back to title.

stats

Defaults to true

boolean

Show the +n −n counts.

switchable

Defaults to false

boolean

Offer the unified / split switch.

emptyText

Defaults to 'No changes.'

string

Shown when nothing changed.

height

Defaults to '32rem'

number | string

Tallest the scroller gets. A number is px, a string any CSS length.

class
string
classes
DiffViewerClasses