Compare HTML source against plain text and highlight the differences.
text-visual-diff extracts visible text from an HTML string (decoding entities along the way), diffs it word-by-word against a target plain-text string, and returns highlighted HTML for both sides — ready to drop into a browser, or consume as structured data.
npm install text-visual-diff
import { diffText } from "text-visual-diff";
const result = diffText(
"<p>Hello <b>world</b></p>",
"Hello earth"
);
console.log(result.highlightedSource);
// <mark class="diff-matched">Hello </mark><b><mark class="diff-unmatched">world</mark></b>
console.log(result.highlightedTarget);
// <mark class="diff-matched">Hello </mark><mark class="diff-added">earth</mark>
console.log(result.extractedText);
// "Hello world"
console.log(result.segments);
// [
// { type: "matched", text: "Hello ", sourceStart: 3, sourceEnd: 9 },
// { type: "unmatched", text: "world", sourceStart: 12, sourceEnd: 17 }
// ]
Main entry point. Strips tags from sourceHtml, decodes HTML entities, diffs the extracted text against targetText, and returns a DiffTextResult.
Compare two plain-text strings and highlight the differences. Unlike diffText, no HTML tags are stripped and no entities are decoded — both inputs are treated as literal plain text.
| Field | Type | Description |
|---|---|---|
highlightedSource |
string |
Source HTML with highlight tags wrapped around matched/unmatched text |
highlightedTarget |
string |
Target plain text with highlight tags wrapped around matched/added text |
extractedText |
string |
Plain text extracted from the source HTML (entities decoded) |
targetText |
string |
The targetText argument, included for convenience |
segments |
HighlightSegment[] |
Structured segments describing each highlighted region in the source |
similarity |
number |
Dice similarity coefficient between 0 and 1 |
All options default to "mark" for tags and "diff-matched" / "diff-unmatched" / "diff-added" for classes.
| Option | Default | Description |
|---|---|---|
matchedTag |
"mark" |
HTML tag for matched regions |
matchedClass |
"diff-matched" |
CSS class for matched regions |
unmatchedTag |
"mark" |
HTML tag for unmatched (removed) regions |
unmatchedClass |
"diff-unmatched" |
CSS class for unmatched regions |
addedTag |
"mark" |
HTML tag for added (target-only) regions |
addedClass |
"diff-added" |
CSS class for added regions |
showWhitespaceDiffs |
false |
When true, highlights whitespace-only differences |
| Field | Type | Description |
|---|---|---|
type |
"matched" | "unmatched" | "added" |
Whether this region matched, was removed, or was added |
text |
string |
The visible text content inside this region |
sourceStart |
number |
Start offset (inclusive) within the original sourceHtml |
sourceEnd |
number |
End offset (exclusive) within the original sourceHtml |
import { useDiffText } from "text-visual-diff/react";
function DiffView({ html, text }) {
const result = useDiffText(html, text);
return <div dangerouslySetInnerHTML={{ __html: result.highlightedSource }} />;
}
<div data-controller="text-visual-diff"
data-text-visual-diff-source-html-value="<p>Hello</p>"
data-text-visual-diff-target-text-value="Hello">
</div>
import { DiffTextController } from "text-visual-diff/stimulus";
application.register("text-visual-diff", DiffTextController);
<script src="https://cdn.jsdelivr.net/npm/text-visual-diff/dist/text-visual-diff.iife.js"></script>
<script>
const result = TextVisualDiff.diffText("<p>Hello</p>", "Hello");
</script>
Two built-in CSS themes are available:
/* Default — green matched, red unmatched, blue added */
@import "text-visual-diff/theme/default";
/* Subtle — lighter tones with dashed underlines */
@import "text-visual-diff/theme/subtle";
You can also write your own — the default class names are diff-matched, diff-unmatched, and diff-added.
MIT