Source Code

<div class="container py-5 d-flex justify-content-center">
  <div class="card bsdiff-card">
    <div class="card-body p-3">
      <div class="d-flex justify-content-between align-items-center mb-2">
        <h6 class="fw-bold mb-0">Changes in v5</h6>
        <span class="small text-muted"><span class="text-success fw-bold" id="bsdiffAdded">0</span> added &middot; <span class="text-danger fw-bold" id="bsdiffRemoved">0</span> removed</span>
      </div>
      <pre class="bsdiff-view mb-0" id="bsdiffView"></pre>
    </div>
  </div>
</div>

Bootstrap Diff Viewer — Free HTML CSS JS Snippet

Bootstrap Diff Viewer · Dashboards · Plain HTML, CSS & JS · Live preview

What's included

Features

A real longest-common-subsequence algorithm, not a naive index-by-index comparison
Correctly identifies unchanged lines even when surrounded by insertions and deletions
Added and removed line counts are derived from the same diff result being displayed, never computed separately
All diffed text is HTML-escaped before rendering, safe against special characters in real content
Distinct, clearly readable visual styles for added, removed, and unchanged lines

About this UI Snippet

Bootstrap Diff Viewer — HTML, CSS & JavaScript

Screenshot of the Bootstrap Diff Viewer snippet rendered live

The naive way to "diff" two lists of lines — compare index 0 to index 0, index 1 to index 1, and so on — breaks the instant a single line is inserted or removed near the top, since every line after that point would then appear "changed" even though most of them are actually identical, just shifted. This snippet instead implements a genuine longest-common-subsequence (LCS) algorithm: a dynamic-programming table dp[i][j] records the length of the longest matching subsequence between the remaining old and new lines from each position, built from the bottom right corner upward.

Walking that table forward from i = 0, j = 0 is what turns the LCS lengths into an actual list of same/add/remove operations — a matching line at the current position advances both pointers as same, and at a mismatch, comparing dp[i+1][j] against dp[i][j+1] decides whether the old line was removed or the new line was inserted, always picking whichever choice preserves the longer eventual match. That's the mechanism that correctly identifies "No credit card required." as unchanged even though it sits at a different combination of surrounding added and removed lines in this demo's before/after text.

Every line's text is HTML-escaped via escapeHtml() before being inserted, so diffed content containing <, >, or & — entirely plausible in real document or config content — can never be misinterpreted as markup once rendered.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Hand this snippet to an AI coding assistant like Claude and ask it to extend the diff to a word-level or character-level granularity for highlighting a small change within a single changed line, or to add a side-by-side (two-column) view as an alternative to the current unified inline view.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a Bootstrap 5.3 line-level text diff viewer, using the real Bootstrap CDN framework (bootstrap.min.css and bootstrap.bundle.min.js) for the surrounding card styling.

Requirements:
- Two sample multi-line texts (an "old" and a "new" version) with at least one line unchanged, at least one line added, and at least one line removed between them.
- Implement a genuine longest-common-subsequence (LCS) diff algorithm using dynamic programming — not a naive line-by-line positional comparison — to correctly identify which lines are unchanged even when insertions or deletions occur elsewhere in the text.
- Render the result as a unified diff: unchanged lines shown plainly, added lines visually highlighted (e.g. green background with a "+" prefix), and removed lines visually struck through (e.g. red background with a "-" prefix).
- Show a live count of added and removed lines above the diff, computed from the same diff result being rendered, not a separately maintained count.
- HTML-escape every line's text before rendering, so diffed content containing special HTML characters displays safely as literal text.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Load the snippetA four-line diff renders: unchanged lines in gray, added lines highlighted green, removed lines struck through in red.
  2. 2
    Read the count above the diffIt reads the real number of added and removed lines, computed from the same diff result being rendered.
  3. 3
    Look at "No credit card required."It renders as unchanged (gray), correctly recognized as identical even though the lines around it changed.
  4. 4
    Compare the last line of each version"Cancel anytime." is shown as removed, and "Trusted by over 2,000 companies." as added — genuinely different lines, not a false "changed" pairing.

Real-world uses

Common Use Cases

DEV
Document and CMS content revision comparisons
Pairs directly with bootstrap-version-history-panel to show exactly what a restore would change before committing to it.
Configuration and settings change review
Show precisely what changed in a JSON config or settings file between two saved states.
Learning the LCS algorithm applied to a real problem
A genuinely useful, complete implementation of the same core algorithm behind tools like git diff.

Got questions?

Frequently Asked Questions

Comparing purely by position breaks as soon as a single line is inserted or removed anywhere before the end — every subsequent line would then appear mismatched even if it's identical, just shifted by one position. LCS correctly finds the actual longest matching subsequence regardless of where insertions or deletions occur.

Whole lines only — this is a line-level diff suitable for prose, config files, or any line-oriented content; a word-level diff (useful for highlighting a small change within a long paragraph) would need a similar LCS approach applied to words instead of lines.

Yes — escapeHtml() runs on every line's text before it's inserted into the page, so content containing <, >, or & characters renders as literal text rather than being interpreted as markup.

Yes. The diffLines() function is plain, framework-agnostic JavaScript — call it with your old/new line arrays in component state and map the result to JSX/template elements instead of building an HTML string directly.