Compare Products Table — Free HTML CSS JS Product Comparison Snippet
Compare Products Table · Tables · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Compare Products Table — HTML, CSS & JavaScript Product Comparison Grid

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:
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
- 1Load the snippetClick "Compare Products Table" in the sidebar Library tab to load the three-column comparison table.
- 2Remove a product columnClick the × button in any product header in the preview and watch the fade-out before the column collapses.
- 3Add feature rowsAdd a new <tr> in the HTML panel with a label cell and one data-product cell per column.
- 4Add 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.
- 5Style the value symbolsReplace the ✓/✕ text with styled SVG icons in the CSS/HTML panels if you want colored check and cross marks.
- 6Export 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
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.