skills/storefront-ui/product-comparison/SKILL.md
Let shoppers select multiple products and compare them side-by-side in a table with highlighted differences to help them make the right buying decision
npx skillsauth add finsilabs/awesome-ecommerce-skills product-comparisonInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Build a side-by-side product comparison feature where shoppers select 2–4 products and see their attributes in a sticky-header table. Attribute rows that are identical across all selected products can be hidden to reduce noise. The comparison state is stored in the URL so it can be shared or bookmarked.
| Platform | Recommended Approach | Why | |----------|---------------------|-----| | Shopify | Install Comparify or Product Compare app (both free tiers available) | Shopify doesn't have built-in comparison; these apps add Compare buttons to product cards, a floating comparison tray, and a full comparison table page; they pull product metafields for spec data | | WooCommerce | Install YITH WooCommerce Compare (free) or WooCommerce Products Compare | YITH Compare is the most popular free option — adds Compare checkboxes to product cards, a comparison table page using WooCommerce product attributes, and a "show differences only" toggle | | BigCommerce | Enable the built-in Compare feature in Storefront → My Themes → Customize (Cornerstone theme) | BigCommerce and Cornerstone include native product comparison — enable it in the Theme Editor; it uses product custom fields as comparison attributes | | Custom / Headless | Build a URL-state comparison tray + comparison table page with your product attribute data | Full control over attribute display, difference highlighting, and table layout; see implementation below |
Using Comparify app:
Preparing your product data:
Using YITH WooCommerce Compare (free):
Preparing your product data:
Built-in comparison (Cornerstone theme):
Adding spec data:
Comparison tray (floating bar as products are selected):
// ComparisonTray.jsx
export function ComparisonTray({ selectedProducts, onRemove, onClear }) {
if (selectedProducts.length === 0) return null;
const compareUrl = `/compare?${selectedProducts.map(p => `compare=${p.id}`).join('&')}`;
return (
<div className="comparison-tray" aria-live="polite" aria-label="Products selected for comparison">
<div className="tray-products">
{selectedProducts.map(product => (
<div key={product.id} className="tray-product">
<img src={product.image} alt={product.name} width="48" height="48" />
<button onClick={() => onRemove(product.id)} aria-label={`Remove ${product.name} from comparison`}>×</button>
</div>
))}
{Array.from({ length: Math.max(0, 4 - selectedProducts.length) }).map((_, i) => (
<div key={`empty-${i}`} className="tray-placeholder" aria-hidden="true">+</div>
))}
</div>
<div className="tray-actions">
<a href={compareUrl} className="btn-primary" aria-disabled={selectedProducts.length < 2}>
Compare ({selectedProducts.length})
</a>
<button onClick={onClear}>Clear all</button>
</div>
</div>
);
}
Comparison table with sticky headers and difference highlighting:
export function ProductComparisonTable({ products, attributeGroups, showOnlyDifferences }) {
function isRowIdentical(attrKey) {
const values = products.map(p => p.attributes[attrKey]);
return values.every(v => v === values[0]);
}
return (
<div className="comparison-wrapper" style={{ overflowX: 'auto' }}>
<table className="comparison-table">
<caption className="sr-only">
Side-by-side comparison of {products.map(p => p.name).join(', ')}
</caption>
<thead>
<tr>
<th scope="col" className="attr-col">Attribute</th>
{products.map(product => (
<th key={product.id} scope="col">
<img src={product.image} alt={product.name} width="80" height="80" />
<a href={product.url}>{product.name}</a>
<strong>${product.price}</strong>
<button className="btn-primary">Add to Cart</button>
</th>
))}
</tr>
</thead>
<tbody>
{attributeGroups.map(group => (
<>
<tr key={`group-${group.label}`}>
<th scope="rowgroup" colSpan={products.length + 1}>{group.label}</th>
</tr>
{group.attributes.map(attrKey => {
if (showOnlyDifferences && isRowIdentical(attrKey)) return null;
return (
<tr key={attrKey} className={isRowIdentical(attrKey) ? 'identical-row' : 'different-row'}>
<th scope="row">{attrKey.replace(/_/g, ' ')}</th>
{products.map(p => (
<td key={p.id}>{p.attributes[attrKey] ?? 'N/A'}</td>
))}
</tr>
);
})}
</>
))}
</tbody>
</table>
</div>
);
}
URL state for comparison (use replaceState to avoid polluting back-button history):
function toggleCompare(productId) {
const params = new URLSearchParams(window.location.search);
const current = params.getAll('compare');
if (current.includes(productId)) {
params.delete('compare');
current.filter(id => id !== productId).forEach(id => params.append('compare', id));
} else if (current.length < 4) {
params.append('compare', productId);
}
window.history.replaceState({}, '', `${window.location.pathname}?${params.toString()}`);
}
overflow-x: auto on a wrapper; never hide columns to fit small screens| Problem | Solution |
|---------|----------|
| Table overflows on mobile | Wrap in a scrollable container; use position: sticky for the first column (attribute labels) |
| Attributes missing for some products | Use "N/A" as the value — never skip the cell as it breaks column alignment |
| Comparison tray covers page content | Add padding-bottom to the page body equal to the tray height when the tray is visible |
| Products have different attribute sets | Normalize attribute keys across all compared products; fill missing values with null |
tools
Let shoppers save products to a wishlist, share it with friends, and get notified when saved items come back in stock or drop in price
development
Build a themeable storefront with design tokens and CSS custom properties that supports white-labeling, multi-brand variants, and dark mode
development
Speed up product discovery with instant search suggestions, fuzzy typo matching, and category-aware results powered by Algolia or Elasticsearch
development
Build a mobile-first storefront with thumb-friendly navigation, sticky add-to-cart buttons, and touch-optimized components for high mobile conversion