webdevpuneet.com

Text Diff Checker — Free HTML CSS JS Word-Level Diff Snippet

Text Diff Checker · Dev · Plain HTML, CSS & JS · Live preview

Category🛠 Dev

Copy & paste

Source Code

<div class="tdc-card">
  <h3>Text Diff Checker</h3>
  <p class="tdc-sub">Word-level comparison between two blocks of text, computed client-side.</p>
  <div class="tdc-inputs">
    <div class="tdc-col">
      <label>Original</label>
      <textarea id="tdcA">The quick brown fox jumps over the lazy dog near the river.</textarea>
    </div>
    <div class="tdc-col">
      <label>Changed</label>
      <textarea id="tdcB">The quick brown fox leaps over the sleepy dog by the river bank.</textarea>
    </div>
  </div>
  <button class="tdc-btn" id="tdcCompare">Compare</button>
  <div class="tdc-result" id="tdcResult">
    <div class="tdc-legend"><span class="tdc-swatch tdc-del"></span> removed <span class="tdc-swatch tdc-add"></span> added</div>
    <div class="tdc-output" id="tdcOutput"></div>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

Real LCS diff algorithm
A dynamic-programming longest common subsequence table drives the comparison, not a naive line-by-line check.
Word-level granularity
Diffs individual words rather than whole lines, so small edits in a long paragraph stay easy to spot.
Whitespace-preserving tokenizer
A capturing-group regex split keeps spacing as real tokens so output reconstruction is exact.
Semantic del/ins markup
Removed and added text render inside real <del> and <ins> elements, not generic styled spans.
HTML-escaped output
Every token is escaped before insertion so pasted text with angle brackets can't break the layout.
Color-coded legend
A small legend clarifies the red-for-removed, green-for-added convention up front.
Responsive two-column input
Side-by-side text areas collapse to a single column on narrow viewports.
Zero dependencies
Pure HTML, CSS, and vanilla JavaScript — no diff-match-patch or similar library.

About this UI Snippet

Text Diff Checker — LCS Dynamic Programming Diff, No Library

Screenshot of the Text Diff Checker snippet rendered live

Comparing two versions of a document, contract, or paragraph word-by-word is exactly the kind of problem that looks trivial until you try to implement it — a naive character comparison produces useless, noisy output. This snippet implements a real longest common subsequence (LCS) diff algorithm from scratch in vanilla JavaScript, operating on whitespace-preserving word tokens instead of individual characters.

Tokenizing without losing whitespace

tokenize() splits on /(\s+)/ with a capturing group, which — unlike a plain split on whitespace — keeps every run of spaces, tabs, and newlines as its own token in the resulting array. That means the diff algorithm treats "quick brown" and "quick brown" (two spaces) as genuinely different token sequences, and reassembling the output never needs to guess where whitespace should go — it's already preserved token-for-token.

Building the LCS table bottom-up

dp[i][j] holds the length of the longest common subsequence between the tail of array a starting at index i and the tail of b starting at index j. The nested loop fills this table from the bottom-right corner backward: dp[i][j] = a[i] === b[j] ? dp[i+1][j+1] + 1 : Math.max(dp[i+1][j], dp[i][j+1]) — if the tokens match, extend the best subsequence found one cell diagonally; otherwise take whichever neighboring cell (skip a token from a, or skip one from b) gives the longer subsequence. This is the same core recurrence used by diff, git diff, and most text-comparison tools.

Backtracking to recover the actual edits

Once the table is built, a forward walk from (0, 0) reconstructs the operations: matching tokens are eq, and at a mismatch the walk follows whichever neighbor cell in the DP table has the larger value — dp[i+1][jj] >= dp[i][jj+1] — deciding whether this token was deleted from a or inserted from b. This greedy-looking walk is provably optimal because the table already encodes the best subsequence length at every position.

Rendering with real <del>/<ins> elements

Deleted tokens render inside <del> and inserted tokens inside <ins> — the semantically correct HTML elements for this exact purpose, not just styled <span>s — while every token is passed through escapeHtml() first so pasted text containing <, >, or & can't break the output markup.

Customizing it

Swap word-level tokenization for character-level (drop the whitespace-preserving regex and split on '') for finer-grained diffs, or add a line-level mode by tokenizing on \n first and running the same diffWords() function on lines instead of words.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSTwo sample paragraphs load with a diff already rendered below.
  2. 2
    Edit either text areaChange the "Original" or "Changed" text to whatever you want to compare.
  3. 3
    Click CompareThe diff output re-renders, showing removed words struck through in red and added words highlighted in green.
  4. 4
    Read the resultUnchanged words appear plain; only the actual differing spans are highlighted, not the whole line.
  5. 5
    Switch to character-level diffingIn tokenize(), remove the whitespace-preserving split and split the string into individual characters instead.
  6. 6
    Add line-level diffingTokenize on newlines first, run diffWords() on the resulting line arrays, then run it again within each changed line for word-level detail.

Real-world uses

Common Use Cases

Contract and legal review
Spot exact wording changes between two versions, with word-level diffs that highlight small edits instead of flagging whole lines.
Copyediting tools
Show writers precisely what an editor changed, using real del and ins elements so deletions and additions carry semantic meaning.
Translation and localisation QA
Compare a translated string against its previous version, with a regex split that keeps whitespace as real tokens.
CMS version comparison
Let editors see word-level differences between saved versions, built on a dynamic-programming longest common subsequence table.
Changelogs and algorithm learning
Highlight what changed in documentation, or study a readable from-scratch LCS implementation that needs no external diff library.
Related: GitHub-Style Contribution Heatmap
See the GitHub-Style Contribution Heatmap for a related dev pattern worth pairing with this one.
Related: Regex Tester & Match Visualizer
See the Regex Tester & Match Visualizer for a related dev pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

It builds a dynamic-programming table where dp[i][j] represents the length of the longest common subsequence between the remaining tokens of each text starting at positions i and j. Filling that table from the end backward, then walking it forward from the start while following whichever neighbor has the larger stored value, reconstructs the optimal sequence of matches, deletions, and insertions.

Character-level diffing on prose tends to produce noisy, hard-to-read output — a single added word can make every following character look "different" until things realign. Word-level tokens keep the diff granularity matched to how humans actually read changes: word by word, not letter by letter.

tokenize() splits using a regex with a capturing group around whitespace, /(\s+)/, which — unlike a normal split — includes the matched whitespace runs as their own array entries instead of discarding them. This means spacing differences are diffed and reconstructed exactly like word differences.

The DP table is O(n × m) in both time and memory, where n and m are the token counts of each text. That's fine for paragraphs and typical documents, but for very large files (thousands of lines) you'd want a line-level pre-pass first — diff at the line granularity, then only run this word-level diff on the lines that actually changed.

Yes — run diffWords() a second time, but on the individual characters of just the words marked del/add adjacent to each other, rather than on the full token arrays. This two-pass approach (word-level first, then character-level within changed spans) is how most polished diff tools get fine-grained highlighting.