Compare Products Table — Free HTML CSS JS Product Comparison Snippet

Compare Products Table · Tables · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

data-product attributes tie every header and row cell for a column together with no JS mapping array
One removeProduct(id) call fades and removes an entire column across every row in a single query
Fade-then-remove sequencing avoids a jarring instant layout jump when a column is dropped
Native table layout reflows remaining columns automatically once cells are actually removed
Horizontal scroll container keeps the table usable on narrow/mobile viewports without squeezing text
Clear visual hierarchy: uppercase feature labels, bold product names, accent-colored prices
Accessible remove buttons with descriptive aria-label text naming the specific plan
Zero dependencies — pure HTML table markup, CSS, and about ten lines of JavaScript

About this UI Snippet

Compare Products Table — HTML, CSS & JavaScript Product Comparison Grid

Screenshot of the Compare Products Table snippet rendered live

Comparison tables are a conversion-critical pattern on pricing and product pages — shoppers scan feature rows across a handful of columns to decide which plan or product fits. This snippet builds a three-column comparison table with a name/price header per product and a per-product remove (×) button, so users can narrow the comparison down as they decide.

How columns are tied together with data-product

Every cell belonging to a given product — its header cell and every row's value cell — carries a matching data-product="1" (or 2, 3) attribute. This is the same "attribute as source of truth" approach used elsewhere in this snippet library: there's no JavaScript array mapping products to columns, the DOM attribute *is* the mapping.

How removing a product works

Clicking the × button calls removeProduct(id), which uses document.querySelectorAll('[data-product="' + id + '"]') to select every cell across the entire table — header and every body row — that belongs to that product, in one query. It adds a .removing class to all of them simultaneously, which triggers a CSS opacity/transform: scale(0.9) transition. After a setTimeout matching the CSS transition duration (200ms), the cells are actually removed from the DOM with .remove(). Removing the cells (rather than just hiding them) automatically causes the browser's native table layout algorithm to redistribute the remaining columns' widths — no manual width recalculation needed.

Why the fade happens before the DOM removal

If .remove() were called immediately on click, the column would vanish instantly with a jarring layout jump. Deferring the actual removal until after the fade transition has time to play gives the user visual feedback that their action registered, then a clean, expected reflow once the fade completes.

Responsive handling

The table sits inside a .compare-wrap with overflow-x: auto and the table itself has a min-width, so on narrow viewports the table scrolls horizontally rather than squeezing columns unreadably thin — a far more usable pattern for data tables than trying to force a comparison grid into a phone-width viewport.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant to explain why removeProduct defers the actual .remove() call behind a setTimeout instead of calling it immediately on click — the short answer is sequencing the visual fade before the layout-affecting DOM change, but it's worth having the assistant walk through what breaks if you skip that step. You can also ask it to make the removal reversible (undo via a stored snapshot of the removed column's data), or to highlight the row-by-row "best value" cell automatically based on which column has the most checkmarks.

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 "compare products table" in plain HTML, CSS, and vanilla JavaScript for a pricing or e-commerce comparison page.

Requirements:
- A table with one feature-label column and three or more product columns, where the header row shows each product's name and price alongside a small round remove (×) button.
- Every cell belonging to a given product column — its header cell and its value cell in every feature row — must share a common data-product attribute value, so a single query can select the entire column.
- Clicking a remove button must select every cell sharing that data-product value in one querySelectorAll call, apply a CSS class that fades and slightly scales them down via transition, and only after that transition's duration has elapsed actually remove the cells from the DOM.
- Removing a column must let the browser's native table layout reflow the remaining columns to fill the freed width — do not manually recalculate column widths in JavaScript.
- The table must sit inside a horizontally scrollable container with a sensible min-width so it degrades gracefully on narrow/mobile viewports instead of squeezing text unreadably.
- Every remove button must have a descriptive aria-label naming the specific product it removes.

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 snippetClick "Compare Products Table" in the sidebar Library tab to load the three-column comparison table.
  2. 2
    Remove a product columnClick the × button in any product header in the preview and watch the fade-out before the column collapses.
  3. 3
    Add feature rowsAdd a new <tr> in the HTML panel with a label cell and one data-product cell per column.
  4. 4
    Add a fourth productAdd a new <th data-product="4"> in the header and a matching <td data-product="4"> in every row, plus its own remove button.
  5. 5
    Style the value symbolsReplace the ✓/✕ text with styled SVG icons in the CSS/HTML panels if you want colored check and cross marks.
  6. 6
    Export and saveExport as HTML/JSX/Tailwind or click "Save as" to reuse this comparison layout for a new pricing page.

Real-world uses

Common Use Cases

SHOP
E-commerce product comparison pages
Let shoppers compare specs across several products and drop ones they've ruled out as they narrow their choice.
PRICE
SaaS pricing tier comparisons
Show feature differences across pricing plans, exactly like the Starter/Pro/Enterprise demo included here.
Interactive shortlisting tools
Use the remove-to-narrow interaction pattern in any tool where users compare several shortlisted options.
Learn attribute-driven table manipulation
Study how a single data attribute lets one querySelectorAll call operate on an entire table column at once.
Internal spec-comparison tools
Reuse the pattern for comparing internal tool tiers, vendor contracts, or hardware specs side by side.

Got questions?

Frequently Asked Questions

Every cell in that column — the header and every row's value cell — shares the same data-product attribute. removeProduct(id) selects all of them in one querySelectorAll call and removes them together.

The removing class triggers a CSS fade/scale transition first. A setTimeout matching that transition's duration (200ms) waits for the animation to visually finish before calling .remove(), so the column disappears smoothly instead of jumping out instantly.

Because the cells are genuinely removed from the DOM (not just hidden), the browser's native table layout algorithm automatically redistributes the remaining columns to fill the available width — no manual recalculation is needed.

Add a new <th data-product="4"> with its own remove button, name, and price in the header row, then add a matching <td data-product="4"> cell with the right value in every existing <tr> in the body.

Yes — just add more <tr> rows. The table has no limit on row count; long tables will simply scroll vertically with the rest of the page.

The table is wrapped in a container with overflow-x: auto and the table itself sets a min-width, so on narrow viewports the user scrolls horizontally to see all columns rather than the text being squeezed illegibly small.

The base version permanently removes the DOM cells. To support re-adding, keep a JS object of each product's full row data and, instead of calling .remove(), hide the column and offer an "Add back" control that re-creates the cells from that stored data.