Source Code

<div class="dft-card">
  <div class="dft-head">
    <h3>Config diff — v1.4 vs v1.5</h3>
    <div class="dft-legend">
      <span><i class="dft-sw dft-added"></i>Added</span>
      <span><i class="dft-sw dft-removed"></i>Removed</span>
      <span><i class="dft-sw dft-changed"></i>Changed</span>
    </div>
  </div>
  <table class="dft-table">
    <thead><tr><th>Key</th><th>Value</th><th>Owner</th><th>Env</th></tr></thead>
    <tbody id="dftBody"></tbody>
  </table>
</div>

Row-Level Diff Table — Compare Two Datasets HTML CSS JS

Row-Level Diff Table · Tables · Plain HTML, CSS & JS · Live preview

What's included

Features

Real keyed row comparison
Rows are matched by a unique identity field via lookup maps, not by array position.
Live-computed added/removed/changed
Every row's status is determined by set membership across both datasets, not hardcoded.
Field-level change detection
Object.keys comparison finds exactly which fields differ per changed row.
Cell-level highlighting
Only the specific differing cells are called out, showing old value struck through above new.
Git-diff visual language
Green/red/yellow with left-border accents mirrors familiar code-review conventions.
Union-of-keys iteration
Every key present in either dataset is included, so nothing added or removed is silently dropped.
Status badges
Small Added/Removed/Changed badges next to the key reinforce the row-level color coding.
Fully data-driven
No precomputed highlight state — editing the source arrays alone updates the rendered diff.

About this UI Snippet

Row-Level Diff Table — Real Computed Added / Removed / Changed Highlighting

Screenshot of the Row-Level Diff Table snippet rendered live

Comparing two versions of a dataset — a config before and after a deploy, a spreadsheet import against the current database, an API response across two environments — is much easier to audit as a single table than as two separate ones. This snippet computes a genuine row-by-row and field-by-field diff between two arrays in plain JavaScript and renders it as one table with added rows in green, removed rows struck through in red, and changed rows in yellow with only the specific cells that actually differ called out.

Keyed comparison, not positional

The diff function builds a lookup map from each dataset keyed by a unique field (key in the demo, but any unique id works), then walks the union of keys present in either version. This is deliberate: comparing row 3 of one array against row 3 of another only works if nothing was reordered, inserted, or removed above it, which is rarely true in practice. Keying by identity means a row that moved position between the two datasets is still correctly matched and compared, not falsely flagged as removed-then-added.

Three real outcomes per key

For every key, the diff falls into exactly one of three buckets computed live: present only in before (removed), present only in after (added), or present in both. For the "present in both" case, Object.keys(a).filter(...) compares every field's value between the two rows and collects which specific fields actually differ — a key with all identical fields is marked unchanged and rendered like a normal row, no highlighting applied.

Cell-level highlighting, not just row-level

A changed row doesn't turn every cell yellow — only the cells whose value actually differs get the dft-cell-changed treatment, showing the old value struck through in small text above the new value in full color. This is what makes the diff genuinely useful: with a row-only highlight you'd know *something* changed but have to hunt for what; the cell-level detail answers that directly, sourced from the changedFields array the comparison function actually computed, not hardcoded per row.

Visual language borrowed from code diffs

The color scheme and left-border accent mirror the added/removed/changed convention from git diffs and code review tools — green for added, red-with-strikethrough for removed, yellow for modified — because that visual vocabulary is already familiar to anyone who reviews changes for a living, making the table legible at a glance without reading the legend.

Fully data-driven, add a row and it just works

Nothing about which rows are added, removed, or changed is precomputed or hand-annotated in the arrays — BEFORE and AFTER are just plain data, and every highlight in the rendered table is the live output of the diff() function running against whatever is currently in those two arrays. Change a value, add a key, remove one, and the table's highlighting updates correctly with no manual bookkeeping.

Customizing it

Swap the identity field, diff nested objects field-by-field recursively, or add a filter to show only changed rows. Pair it with an editable table for the "before" state or a sortable table to reorder the diff results.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Rather than tracing the comparison logic by hand, paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why the diff is built from lookup maps keyed by identity rather than comparing the two arrays by index position, and what specifically would go wrong (false added/removed pairs) if a row were simply reordered between the two datasets under a positional comparison. The same assistant can help you extend it — ask it to add deep comparison for nested object fields, a summary count of how many rows were added/removed/changed, or a toggle to hide unchanged rows so only the meaningful diff is visible. It is also useful for optimization: ask whether building two full lookup maps is worth it for very large datasets versus a single-pass approach. Treat the code less like a finished artifact and more like a starting point for a conversation.

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 "row-level diff table" in plain HTML, CSS, and JavaScript with no library — a table that compares two arrays of row objects (a "before" and "after" version of the same dataset) and highlights what changed.

Requirements:
- Write a pure diff(before, after) function that builds a lookup map from each array keyed by a unique identity field (not by array index), then computes the union of all keys present in either dataset.
- For every key in that union, determine its status by real set membership: present only in before is "removed", present only in after is "added", present in both with at least one differing field is "changed", present in both with all fields identical is "unchanged" — do not hardcode which rows fall into which category.
- For a "changed" row, compute exactly which fields differ (not just that something differs) by comparing each field's value between the before and after versions of that row, and store that list of changed field names.
- Render one table where added rows get a distinct highlight color (e.g. green) with a left-border accent, removed rows get a different highlight color with the entire row's text shown struck through, and changed rows get a third highlight color — but only the specific cells whose field was in the changed-fields list should show a special highlighted treatment (old value struck through in small text, new value in full color below or beside it); untouched cells in a changed row render normally.
- Add a small badge label (Added/Removed/Changed) next to the row's identity value reinforcing its status, and a legend at the top of the table explaining what each color means.
- Make the whole table fully derived from the diff() function's output — editing the before or after source arrays and re-running the diff must update every highlight and badge with no manually maintained highlight state anywhere.

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
    Paste HTML, CSS, and JSA config diff renders comparing two sample datasets, BEFORE and AFTER.
  2. 2
    Read the legendGreen marks added rows, red-strikethrough marks removed rows, yellow marks changed rows.
  3. 3
    Inspect a changed rowOnly the specific cells that differ are highlighted, showing the old value struck through above the new one.
  4. 4
    Edit BEFORE or AFTERAdd, remove, or change a value in either array — the diff recomputes and the highlighting follows automatically.
  5. 5
    Change the identity keySwap 'key' for any unique field in your data — the comparison is keyed, not positional.
  6. 6
    Add more fieldsAdd properties to the row objects; changedFields is computed generically over Object.keys, so new fields diff automatically.

Real-world uses

Common Use Cases

Config and feature-flag diffs
Compare configuration or flag state across deploys, environments, or points in time.
Database migration review
Show what an import or migration will add, remove, or change before committing it.
API response comparison
Diff a response across two environments or API versions field by field.
Approval and audit workflows
Pair with an editable table so reviewers can see proposed edits before approving.
Spreadsheet import review
Compare an uploaded CSV against existing records before applying a bulk update.
Version comparison tools
A row-level counterpart to a comparison table for comparing two states of the same entity over time.
Related: Expandable Row Detail Table
See the Expandable Row Detail Table for a related tables pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

The diff function marks it status: 'unchanged' since changedFields comes back empty, and the row renders with no highlight class and no badge — it looks like a completely normal row, which is the correct behavior since nothing about it actually changed.

Build a composite key by concatenating multiple fields (e.g. row.category + '|' + row.name) when constructing beforeMap and afterMap, and use that same composite when building the keys union array — the rest of the diff logic is unaffected since it only cares that the key uniquely identifies a row.

Replace the simple a[field] !== b[field] comparison with a deep-equality check (a small recursive function, or JSON.stringify(a[field]) !== JSON.stringify(b[field]) for simple cases) so a field whose value is itself an object or array is correctly detected as changed only when its contents actually differ.

Filter the results array from diff() with results.filter(function(e){ return e.status !== 'unchanged'; }) before mapping it to row HTML — the diff computation itself doesn't need to change, only what you choose to render from its output.

Keep the diff(before, after) function exactly as-is — it's pure JavaScript with no DOM dependency, so wrap it in a useMemo (React), computed (Vue), or getter (Angular) keyed on your before/after data, and map the returned array to row components applying the same status-based classes.