
Product Page Design
- 154 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Lay out high-converting product detail pages with gallery, pricing, social proof, variant pickers, and trust elements tuned for clarity, accessibility, and mobile purchase flows.
About
Product-page-design from awesome-ecommerce-skills codifies PDP UX patterns for ecommerce: imagery hierarchy, variant controls, urgency and trust modules, and mobile-first layouts that keep add-to-cart obvious while presenting specs and policies without clutter.
- Structures hero media, title, price, and primary CTA
- Places reviews, badges, and shipping info for trust
- Designs variant selectors with clear state feedback
- Balances SEO-friendly copy blocks with scannable UI
- Optimizes thumb-zone actions on mobile PDPs
Product Page Design by the numbers
- 154 all-time installs (skills.sh)
- Ranked #1,003 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill product-page-designAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 154 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Lay out high-converting product detail pages with gallery, pricing, social proof, variant pickers, and trust elements tuned for clarity, accessibility, and mobile purchase flows.
Files
Product Page Design
Overview
Build high-converting product detail pages (PDP) with zoomable image galleries, variant selectors (size, color, material), quantity controls, and social proof elements. This skill covers responsive layout patterns, platform-specific customization, and the component choices that drive conversion — from above-the-fold hero sections to sticky add-to-cart bars on mobile.
When to Use This Skill
- When building a product detail page from scratch for a new storefront
- When optimizing an existing PDP for higher add-to-cart conversion rates
- When implementing a variant selector that handles multiple option types (size + color)
- When adding an image gallery with zoom, thumbnails, and swipe support
- When integrating social proof elements like reviews, ratings, and stock indicators
Core Instructions
Step 1: Determine the merchant's platform and choose the right approach
| Platform | Recommended Approach | Why |
|---|---|---|
| Shopify | Use OS2.0 theme (Dawn, Sense, Craft) and customize via Theme Editor; extend with apps for reviews (Judge.me, Loox) and variant displays | Dawn's product template handles gallery, variants, and Add to Cart out of the box; Theme Editor lets merchants configure layout without code |
| WooCommerce | Use WooCommerce's built-in product page with a well-supported theme (Astra, Kadence, Flatsome) + Storefront Customizer; extend with YITH, WooCommerce Product Add-Ons, and WP Review plugins | WooCommerce provides the gallery, variants (attributes + variations), and cart button natively; theme selection determines layout quality |
| BigCommerce | Customize the Cornerstone theme's product page via Theme Editor; use BigCommerce's built-in review system and extend with Shogun for advanced layout control | Cornerstone covers all core PDP components; BigCommerce's Theme Editor exposes layout options without code |
| Custom / Headless | Build a two-column grid layout with a gallery component, variant selector, sticky add-to-cart bar, and structured data for SEO | Full control over every component and interaction; see patterns below |
Step 2: Configure the PDP layout and components
---
Shopify
Layout configuration (Theme Editor): 1. Go to Online Store → Themes → Customize 2. Navigate to Products → Default product template 3. Configure the Product information section:
- Enable Quantity selector if you sell multi-unit products
- Enable Payment button (Buy it now / Shop Pay express checkout) below Add to Cart
- Set Variant selector style: Dropdown for many options, Buttons for size/color swatches
4. In the Product media section, set Media size to Large or Extra Large for better zoom quality 5. Enable the Sticky header option if your theme supports it — keeps Add to Cart visible on scroll
Adding reviews (most important social proof element):
- Install Judge.me Product Reviews (free tier — unlimited reviews, photo reviews) from the Shopify App Store
- Or install Loox (photo/video reviews with referral program, from $9.99/mo)
- Both apps inject review stars below the product title and a full reviews section at the bottom of the page with no theme code changes
Improving variant selectors for visual products (colors/sizes):
- Install Variant Image Selector or use a theme with built-in swatch support (Prestige, Impulse, Flex)
- In Dawn: go to Theme settings → Variant pills and enable them to replace dropdowns with clickable buttons
- For color swatches with images: use the Variant Image feature to assign a distinct image to each color variant; the gallery switches automatically
Stock urgency indicator:
- Install Urgency Bear or Hurrify app for "Only X left" badges — these read your actual Shopify inventory, not fake numbers
- Or configure inventory thresholds in Products → [product] → Inventory and enable the built-in low stock notification in your theme
---
WooCommerce
Layout configuration: 1. Go to WooCommerce → Settings → Products → Display 2. Set Product images size to Large (at minimum 600×600px) for zoom quality 3. Enable Product gallery zoom in WooCommerce settings to activate hover zoom
Theme-level layout:
- Astra: Go to Appearance → Customize → WooCommerce → Product Page — configure single-column or two-column layout, gallery position, and sticky Add to Cart
- Flatsome: Use the built-in drag-and-drop UX Builder to rearrange PDP sections; Flatsome has built-in sticky Add to Cart and swatch selectors
- Kadence: In Kadence → WooCommerce settings, enable sticky Add to Cart, set gallery layout (vertical thumbnails, horizontal strip), and configure review stars placement
Adding reviews:
- Install WooCommerce Product Reviews Pro (WooCommerce.com, $79/yr) for photo reviews, review reminders, and helpful votes
- Or use Customer Reviews for WooCommerce (free) which integrates with Trustpilot and Google Shopping
Variant selectors (color swatches, size buttons):
- Install Variation Swatches for WooCommerce (free, WordPress.org) — converts attribute dropdowns to visual swatches (color, image, or button style)
- Configure swatch type per attribute in Products → Attributes → [attribute] → Swatch type
---
BigCommerce
Layout configuration (Cornerstone theme): 1. Go to Storefront → My Themes → Customize 2. Under Product Page, configure:
- Image layout: Carousel, Thumbnail strip vertical, or Thumbnail strip horizontal
- Gallery zoom: Enable hover zoom
- Product options display: Dropdown or Swatch buttons per option type
3. Enable Sticky Add to Cart if your theme version supports it (Cornerstone 6+)
Reviews: 1. Go to Storefront → Product Reviews and enable the built-in review system 2. Set minimum review length and whether reviews require approval before publishing 3. For advanced review features (photo reviews, Q&A, review emails): install PowerReviews or Yotpo from the BigCommerce App Marketplace
Social proof apps:
- Judge.me and Loox are available for BigCommerce in addition to Shopify
---
Custom / Headless
Two-column responsive PDP layout:
.pdp-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
}
@media (max-width: 768px) {
.pdp-grid { grid-template-columns: 1fr; }
}
/* Gallery sticks while scrolling through product info on desktop */
.pdp-gallery { position: sticky; top: 2rem; align-self: start; }Variant selector with availability tracking:
// VariantSelector.jsx — uses radio inputs for accessibility
function VariantSelector({ product, selectedOptions, onOptionChange }) {
function isAvailable(optionName, value) {
return product.variants.some(v =>
v.options[optionName] === value &&
v.inventory > 0 &&
Object.entries(selectedOptions).every(([k, sv]) =>
k === optionName || v.options[k] === sv
)
);
}
return (
<div className="variant-selector">
{product.optionNames.map(optionName => (
<fieldset key={optionName}>
<legend>{optionName}: <strong>{selectedOptions[optionName]}</strong></legend>
{product.optionValues[optionName].map(value => (
<label key={value} className={!isAvailable(optionName, value) ? 'unavailable' : ''}>
<input type="radio" name={optionName} value={value}
checked={selectedOptions[optionName] === value}
disabled={!isAvailable(optionName, value)}
onChange={() => onOptionChange(optionName, value)}
className="sr-only" />
<span className="option-btn">{value}</span>
{!isAvailable(optionName, value) && <span className="sr-only"> (out of stock)</span>}
</label>
))}
</fieldset>
))}
</div>
);
}Sticky Add to Cart bar (appears when primary button scrolls out of view):
function StickyBuyBar({ product, selectedVariant, onAddToCart }) {
const [visible, setVisible] = useState(false);
useEffect(() => {
const el = document.getElementById('main-add-to-cart');
if (!el) return;
const obs = new IntersectionObserver(([e]) => setVisible(!e.isIntersecting));
obs.observe(el);
return () => obs.disconnect();
}, []);
return visible ? (
<div className="sticky-buy-bar" role="complementary" aria-label="Add to cart">
<span>{product.name}</span>
<span>${selectedVariant?.price ?? product.price}</span>
<button onClick={onAddToCart} disabled={!selectedVariant || selectedVariant.inventory <= 0}>
{selectedVariant?.inventory <= 0 ? 'Sold Out' : 'Add to Cart'}
</button>
</div>
) : null;
}Product structured data for SEO (JSON-LD):
// Build server-side to avoid client-side injection
function buildProductSchema(product, reviews) {
return {
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
image: product.images.map(i => i.src),
description: product.description,
sku: product.sku,
brand: { '@type': 'Brand', name: product.brand },
offers: {
'@type': 'AggregateOffer',
lowPrice: Math.min(...product.variants.map(v => v.price)).toFixed(2),
priceCurrency: 'USD',
availability: product.inStock ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock',
},
...(reviews.totalCount > 0 && {
aggregateRating: {
'@type': 'AggregateRating',
ratingValue: reviews.averageRating.toFixed(1),
reviewCount: reviews.totalCount,
},
}),
};
}Step 3: Add social proof and trust signals
Regardless of platform, these elements directly improve add-to-cart conversion:
1. Star ratings near the title — place review average and count immediately below the product name, above the price 2. Stock indicators — show "Only 3 left" only when inventory is genuinely low (under 10 units); never fake urgency 3. Trust badges — secure checkout, free returns, and warranty symbols near the Add to Cart button; most themes have a trust badge section or use a free app 4. Recent purchase notifications — apps like Sales Pop (Shopify) or WooCommerce Sales Popup show "Someone in [city] bought this 2 hours ago" — use with real data only
Best Practices
- Prioritize above-the-fold content — title, price, main image, and Add to Cart button should be visible without scrolling on desktop
- Use semantic HTML for variant selectors —
<fieldset>/<legend>/<input type="radio">pattern, not clickable<div>elements; screen readers navigate by this - Lazy-load below-the-fold images — only the hero image uses
loading="eager"; thumbnails and secondary images useloading="lazy" - Show compare-at prices clearly — display the original price with a strikethrough next to the sale price
- Disable, don't hide, out-of-stock variants — show them as disabled so users understand what exists in the product
- Use real inventory data for urgency — never fake "only 3 left" messaging; use actual stock counts or remove the indicator
Common Pitfalls
| Problem | Solution |
|---|---|
| CLS from image loading | Set explicit width and height on images, or use aspect-ratio on the container |
| Variant selector doesn't update the URL | Use replaceState to update the URL with the selected variant ID so the page is shareable |
| Add-to-cart button hidden on long pages (mobile) | Implement a sticky add-to-cart bar that appears when the main button scrolls out of view |
| Gallery images load slowly on mobile | Serve responsive images with srcset and use WebP/AVIF formats |
| Reviews section causes long initial load | Lazy-load reviews — initialize the section only when the user scrolls near it |
Related Skills
- @image-zoom-360
- @responsive-storefront
- @accessibility-commerce
- @checkout-flow-optimization
- @search-autocomplete
{
"context": "Tests whether the agent builds a product image gallery that correctly implements lazy/eager loading, hover-based zoom, accessible thumbnail markup, CLS prevention, mobile touch support, keyboard navigation, and hero image preload.",
"type": "weighted_checklist",
"checklist": [
{
"name": "First image eager loading",
"max_score": 8,
"description": "The main/hero image (index 0) uses loading=\"eager\" (not lazy)"
},
{
"name": "Thumbnail lazy loading",
"max_score": 8,
"description": "Thumbnail images use loading=\"lazy\""
},
{
"name": "Explicit image dimensions",
"max_score": 8,
"description": "All img elements have explicit width and height attributes set, OR the main image container uses aspect-ratio CSS property"
},
{
"name": "Hover zoom via backgroundPosition",
"max_score": 10,
"description": "Zoom is implemented by updating CSS backgroundPosition based on mouse coordinates (not by scaling with transform or opening a modal)"
},
{
"name": "Zoom backgroundSize value",
"max_score": 7,
"description": "The zoomed background uses backgroundSize of '200%' (or equivalent — a numeric value >= 150% that enables partial-image inspection)"
},
{
"name": "Thumbnail container ARIA",
"max_score": 8,
"description": "The thumbnails container element has role=\"listbox\" and an aria-label attribute"
},
{
"name": "Thumbnail button ARIA",
"max_score": 8,
"description": "Each thumbnail button has aria-selected (reflecting whether it is active) and an aria-label attribute"
},
{
"name": "Mobile touch-action",
"max_score": 9,
"description": "The gallery main image container has touch-action: none applied (in CSS or inline style) to support pinch-to-zoom on touch devices"
},
{
"name": "Gallery keyboard navigation",
"max_score": 10,
"description": "Pressing left/right arrow keys changes the active image (a keyboard event handler is present on the gallery or a wrapping element)"
},
{
"name": "Hero preload link",
"max_score": 8,
"description": "A <link rel=\"preload\" as=\"image\"> tag is generated or documented for the first product image to improve LCP"
},
{
"name": "Gallery sticky positioning",
"max_score": 8,
"description": "The gallery container uses position: sticky with a top offset in CSS so it stays in view while the user scrolls the product info column"
},
{
"name": "Cursor zoom feedback",
"max_score": 8,
"description": "The main image container has cursor: zoom-in when not zoomed and cursor: zoom-out when zoomed"
}
]
}
Product Gallery Component
Problem/Feature Description
A fashion retailer is rebuilding their product detail pages in React/TypeScript. Their current gallery is a simple <img> tag with no zoom capability, causes significant layout shift on page load, and performs poorly on Google's Core Web Vitals audit — particularly LCP and CLS scores. Their mobile users (60% of traffic) also complain they can't zoom into images to inspect fabric details.
The team needs a polished ProductGallery component that handles the main display image with zoom, a row of clickable thumbnails, and proper handling of touch devices. The component should be accessible to keyboard and screen reader users, and should be optimized for fast initial loads without impacting the perceived quality of the images.
Output Specification
Produce a self-contained ProductGallery.tsx React/TypeScript component file. The component should accept an array of product images (each with at minimum src, alt, width, and height fields) and render a main display area with zoom interaction and a thumbnail strip below.
Also produce a ProductGallery.css file with the supporting styles.
You do not need to set up a full app — just the component files are required. Include a brief IMPLEMENTATION_NOTES.md describing any non-obvious decisions you made about zoom behavior on desktop vs. mobile.
{
"context": "Tests whether the agent implements correct stock indicator thresholds, avoids fake urgency, uses ProductJsonLd or builds correct JSON-LD with conditional aggregateRating, lazy-loads the reviews section, serves responsive images with srcset/WebP, and uses role=status on the stock warning.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Stock indicator > 10 threshold",
"max_score": 8,
"description": "StockIndicator renders nothing (returns null or empty) when inventory count is greater than 10"
},
{
"name": "Stock low message threshold",
"max_score": 8,
"description": "StockIndicator shows a low-stock warning (e.g. 'Only N left') when inventory is between 1 and 10 inclusive"
},
{
"name": "Out of stock message",
"max_score": 8,
"description": "StockIndicator shows an out-of-stock message (e.g. 'Out of stock') when inventory is 0 or less"
},
{
"name": "Stock status ARIA role",
"max_score": 7,
"description": "The low-stock warning element has role=\"status\" to announce the message to screen readers"
},
{
"name": "Real inventory data only",
"max_score": 9,
"description": "Stock/urgency indicators are driven by a real inventory value from props/hook — no hardcoded 'Only 3 left' or similar fabricated low-stock text"
},
{
"name": "JSON-LD @type Product",
"max_score": 8,
"description": "Structured data output uses @type: 'Product' and @context: 'https://schema.org' (via ProductJsonLd component or a manually built JSON-LD script tag)"
},
{
"name": "JSON-LD required fields",
"max_score": 8,
"description": "Structured data includes all of: name, image (array), description, sku, and brand fields"
},
{
"name": "JSON-LD aggregateRating conditional",
"max_score": 9,
"description": "aggregateRating is only included in the structured data when totalCount > 0 — it is omitted or undefined when there are no reviews"
},
{
"name": "Lazy-loaded reviews",
"max_score": 10,
"description": "The reviews section is lazy-loaded using IntersectionObserver, React.lazy/Suspense, dynamic import, or similar — it is NOT rendered/fetched eagerly on initial page load"
},
{
"name": "Responsive images srcset",
"max_score": 8,
"description": "Product images use the srcset attribute (or next/image or similar) to serve different sizes for different viewport widths"
},
{
"name": "Modern image format",
"max_score": 9,
"description": "Images are served in WebP or AVIF format (via srcset type, <picture> source element, or next/image format configuration)"
},
{
"name": "JSON-LD availability mapping",
"max_score": 8,
"description": "The offers availability field uses schema.org URIs: 'https://schema.org/InStock' or 'https://schema.org/OutOfStock' (not plain strings like 'in stock')"
}
]
}
Product Page — Social Proof, SEO, and Performance
Problem/Feature Description
A home goods brand has a working product detail page, but their SEO team reports that Google Shopping is not picking up their products correctly and their rich results test shows no structured data. They also want to add social proof elements to improve conversion: customer ratings, a stock availability indicator, and a recent purchases notification. However, the marketing team has strict guidelines against misleading customers — all urgency messaging must be based on real data, not manufactured scarcity.
Their frontend developer also flagged two performance issues: the large unoptimized product images are causing slow loads on mobile connections, and the reviews section (which fetches from an external API) is slowing down initial page render for users who never even scroll to it.
Output Specification
Produce the following files:
ProductSeo.tsx— a component that injects SEO structured data for the productSocialProof.tsx— a component containing the rating summary, stock indicator, and recent purchase notificationStockIndicator.tsx— a standalone stock status componentIMPLEMENTATION_NOTES.md— a brief explanation of how structured data is constructed, the stock indicator thresholds used, the approach to lazy-loading reviews, and how images are served for performance
Use the following mock data for the product (inline it in your components or a mockData.ts):
Product title: "Linen Duvet Set"
SKU: "LDS-001"
Brand: "Casa Nova"
Description: "100% stonewashed linen duvet set, available in natural and dove grey."
Images: ["/images/linen-duvet-1.jpg", "/images/linen-duvet-2.jpg"]
Price: 149.00 USD
In stock: true
Reviews: { averageRating: 4.7, totalCount: 83 }Also produce a productSchema.ts file exporting a buildProductSchema(product, reviews) function that constructs the full JSON-LD object.
Do not set up a bundler — just the source files are needed.
{
"context": "Tests whether the agent implements a two-column PDP layout (gallery left, info right), accessible variant selectors with disabled-not-hidden unavailable options, a sticky add-to-cart bar using IntersectionObserver, compare-at price strikethrough display, and URL variant state via replaceState.",
"type": "weighted_checklist",
"checklist": [
{
"name": "Two-column CSS grid",
"max_score": 7,
"description": "The page layout uses CSS grid with grid-template-columns: 1fr 1fr (or equivalent equal-width two columns) for desktop"
},
{
"name": "Gallery left, info right",
"max_score": 7,
"description": "The gallery/image section appears as the first column (left) and the product info section as the second column (right) in the desktop layout"
},
{
"name": "Mobile single column",
"max_score": 6,
"description": "A media query at max-width: 768px (or similar breakpoint) collapses the layout to a single column"
},
{
"name": "Unavailable variants disabled",
"max_score": 10,
"description": "Out-of-stock variant option buttons are rendered with the disabled attribute (or aria-disabled), NOT removed from the DOM"
},
{
"name": "Variant fieldset/legend",
"max_score": 8,
"description": "Each option group (Color, Size) is wrapped in a <fieldset> with a <legend> element"
},
{
"name": "Variant radiogroup role",
"max_score": 8,
"description": "The container of variant option buttons has role=\"radiogroup\" and each button has role=\"radio\" with aria-checked"
},
{
"name": "IntersectionObserver sticky ATC",
"max_score": 10,
"description": "StickyAddToCart uses IntersectionObserver to watch the main add-to-cart button and shows only when that button is NOT intersecting the viewport"
},
{
"name": "Sticky ATC hidden on desktop",
"max_score": 8,
"description": "The sticky add-to-cart bar is hidden on desktop screens via a CSS media query (min-width: 769px or similar) with display: none"
},
{
"name": "Compare-at price strikethrough",
"max_score": 8,
"description": "The original/compare-at price ($249.00) is rendered with a text-decoration: line-through style alongside the sale price ($189.00)"
},
{
"name": "URL variant replaceState",
"max_score": 10,
"description": "When a variant option is changed, history.replaceState (or equivalent) is called to update the URL with the selected variant identifier"
},
{
"name": "Max-width constraint",
"max_score": 8,
"description": "The page grid container has a max-width set (e.g., 1280px or similar) to prevent excessive stretching on very wide screens"
},
{
"name": "Sticky ATC fixed position",
"max_score": 10,
"description": "The sticky add-to-cart bar uses position: fixed with bottom: 0 and spans the full viewport width"
}
]
}
Product Detail Page — Variants, Layout, and Add to Cart
Problem/Feature Description
An outdoor apparel brand is launching a new storefront built with React and TypeScript. Their flagship jacket comes in 3 colors and 5 sizes, but not every color–size combination is in stock. The product team wants a product detail page that lets customers confidently browse and select a variant — they need to see which combinations exist (even when out of stock) so they can decide whether to wait for a restock or choose an alternative.
The page also needs a persistent way for mobile shoppers to add to cart without scrolling back to the top. Analytics shows that on mobile, 40% of add-to-cart clicks on competitor sites happen from persistent bottom bars, not from the main product section. The URL should also reflect the selected variant so customers can share specific configurations with friends.
When the jacket is on sale, the page must clearly communicate the saving — the original price should remain visible next to the discounted price.
Output Specification
Produce the following files:
ProductPage.tsx— the main product detail page componentVariantSelector.tsx— the variant selection componentStickyAddToCart.tsx— the sticky add-to-cart bar componentProductPage.css— layout and component styles
Use the following product data (inline it into your code or a separate mockProduct.ts):
Product: Summit Shell Jacket
Variants: combinations of Color (Midnight Blue, Forest Green, Burnt Orange) × Size (XS, S, M, L, XL)
In-stock combinations:
Midnight Blue: S, M, L
Forest Green: XS, S, L, XL
Burnt Orange: M, XL
Price: $189.00, compare-at price: $249.00Do not set up a bundler or dev server — just produce the component source files. Include a short DECISIONS.md noting any layout or behavior decisions.
{
"name": "finsi/product-page-design",
"version": "0.1.0",
"summary": "High-converting product page layouts with image galleries, variant selectors, and social proof",
"skills": {
"product-page-design": {
"path": "SKILL.md"
}
}
}