# Product Detail Page Layout

## Contents

- [Overview](#overview)
- [Layout Structure](#layout-structure)
- [Price Display and Medusa Integration](#price-display-and-medusa-integration)
- [Variant Selection (Critical)](#variant-selection-critical)
- [Stock Availability](#stock-availability)
- [Add to Cart Behavior](#add-to-cart-behavior)
- [Product Details Organization](#product-details-organization)
- [Related Products Strategy](#related-products-strategy)
- [Trust Signals and Conversion](#trust-signals-and-conversion)
- [Mobile Optimization](#mobile-optimization)
- [Checklist](#checklist)

## Overview

Most critical page for conversion. Customers make purchase decisions here based on product information, images, reviews, and trust signals.

### Key Requirements

- High-quality product images with zoom capability
- Clear price display (handle variant price changes)
- Variant selection (size, color, material)
- Stock availability indicators
- Prominent "Add to Cart" with proper feedback
- Product details (description, specifications)
- Customer reviews and ratings
- Related product recommendations
- Trust signals (shipping, returns, secure checkout)
- Mobile-optimized (60%+ traffic)

### Routing Pattern

**CRITICAL: Always use dynamic routes, NEVER static pages.**

Product detail pages must use dynamic routes that accept a parameter (handle, slug, or ID):

**Correct examples:**
- Next.js App Router: `app/products/[handle]/page.tsx`
- Next.js Pages Router: `pages/products/[handle].tsx`
- SvelteKit: `routes/products/[handle]/+page.svelte`
- TanStack Start: `routes/products/$handle.tsx`
- Remix: `routes/products.$handle.tsx`

**Wrong examples:**
- ❌ `pages/products/blue-shirt.tsx` (static file per product)
- ❌ `pages/products/red-shoes.tsx` (doesn't scale)

Fetch product data in the dynamic route based on the handle/ID parameter from the URL.

## Layout Structure

**Desktop (two-column):**
- Left: Product images (50-60% width)
- Right: Product info, variants, add to cart (40-50%)
- Below: Product details, reviews, related products (full-width)

**Mobile (stacked):**
- Images at top (full-width, swipeable)
- Product info below (title, price, rating)
- Variants and add to cart
- Accordion for product details
- Reviews section
- Related products
- Sticky "Add to Cart" bar at bottom

**Sticky sidebar option (desktop):**
- Product info column stays visible during scroll
- Add to cart always accessible
- Useful for long product descriptions
- Improves conversion

## Price Display

### Standard Price Display

**Current price:**
- Large, bold font (28-36px)
- Currency symbol included ($49.99)
- Primary color or black

**Sale pricing:**
- Original price with strikethrough: ~~$79.99~~ $49.99
- Sale price in red or brand color
- "Save X%" badge nearby
- Example: Save 37%

**Variant price changes:**
- **When no variant selected**: Show "From $X" where X is the minimum variant price across all variants
- **When variant selected**: Update price dynamically to show the exact variant price
- No page reload required
- Show price change clearly (highlight briefly on change)
- Example: Product with variants priced at $29.99, $34.99, $39.99 → Show "From $29.99" initially

### Medusa Pricing (CRITICAL)

**Important difference from Stripe:**
- Medusa stores prices as-is (e.g., 49.99)
- Display directly: If API returns 49.99, show $49.99
- **DON'T divide by 100** (unlike Stripe which stores in cents)
- Example: Medusa 49.99 → Display $49.99 (NOT $0.4999)

**Multi-currency (Medusa):**
- Medusa supports multi-region pricing
- Display price in user's region currency
- Fetch pricing from selected region
- Show currency code (usd, eur, etc.)

## Variant Selection (Critical)

This is a complex ecommerce-specific challenge. Variants affect price, stock, and images.

### Variant Complexity

**Key challenges:**
- Multiple variant types (size, color, material)
- Variant availability varies (some sizes out of stock)
- Prices may differ by variant
- Images change by color variant
- Stock levels per variant
- Combinations may not exist (size M + color Red might not exist)

**Fetch from backend:**
```typescript
// Get all variants for product
// Change this based on the backend integrated
const product = await fetch(`/products/${id}?fields=*variants`)
// Returns variants with: id, sku, options, calculated_price, inventory_quantity
```

### Variant Selection Patterns

**Use Button Group when:**
- 2-8 options per variant type
- Size selection (XS, S, M, L, XL)
- Simple color options (5-6 colors)
- Users need to see all options at once

**Benefits:**
- Visible options (no click to reveal)
- Faster selection
- Clear visual feedback
- Better UX

**Use Dropdown when:**
- 10+ options per variant type
- Material/style options with long names
- Space-constrained layouts
- Mobile optimization needed

**Benefits:**
- Saves space
- Works better for many options
- Mobile-friendly

**Use Visual Swatches when:**
- Color or pattern variations
- Material with visual differences
- Visual is key to decision
- Fashion, home decor, customizable products

**Implementation:**
- Circular/square swatches (40-48px)
- Border on selected
- Show product image in that color when selected
- Color name on hover
- Gray out unavailable colors

### Variant Selection Flow

**Critical sequence:**
1. User selects first variant type (e.g., Color: Blue)
2. **Update available options** for other variant types
3. Show only size options available for Blue color
4. Gray out/disable unavailable combinations
5. Update price if variant price differs
6. Update main product image to show selected variant
7. Update stock availability
8. Enable/disable "Add to Cart" based on availability

**Example: Two variants (Color + Size)**
```typescript
// When color selected
// Change this based on the backend integrated
onColorSelect(color) {
  // Find selected variant
  const selectededVariant = product.variants.find((variant) => variant.options?.every(
    (optionValue) => optionValue.id === selectedOptions[optionValue.option_id!]
  ))

  // Check if size is selected and update price
  if (selectededVariant) {
    const variant = findVariant(color, selectedSize)
    updatePrice(variant.price)
    updateStock(variant.inventory_quantity)
  }
}
```

### Validation and Error Handling

**Prevent adding without selection:**
- Disable "Add to Cart" until all required variants selected
- Or: Show error message "Please select a size"
- Highlight missing selection (red border around options)
- Scroll to variant selection on error

**Handle out of stock variants:**
- Gray out unavailable options
- "Out of stock" text on hover
- Don't allow selection of out of stock variants
- Suggest alternative variants if available

**Handle variant not found:**
- When combination doesn't exist (Size M + Color Red)
- Disable second option when first selected
- Show only valid combinations
- Or: Show "This combination is not available"

## Stock Availability

**Display patterns:**

**In stock:**
- Green indicator (✓ or dot)
- "In stock" or "Available"
- Quantity if low: "Only 3 left"
- Encourages urgency without being pushy

**Out of stock:**
- Red indicator (✗ or dot)
- "Out of stock" message
- Disable "Add to Cart" button (grayed out)
- Offer "Notify me when available"
- Email capture for restock notifications (if supported by backend)

**Low stock warning:**
- "Only X left in stock"
- Shows scarcity (increases urgency)
- Typically show when <= 5 items
- Orange/yellow color

**Pre-order:**
- "Pre-order now" status
- Expected availability date: "Ships on [Date]"
- Different button text: "Pre-order" instead of "Add to Cart"
- Charge now or later (specify)

**Backend integration:**
```typescript
// Fetch stock for selected variant
const stock = selectedVariant.inventory_quantity

if (stock === 0) {
  showOutOfStock()
} else if (stock <= 5) {
  showLowStock(stock) // "Only 3 left"
} else {
  showInStock()
}
```

## Add to Cart Behavior

**Button states:**
- Default: Enabled (after variant selected)
- Hover: Slight color change or scale
- Loading: Spinner inside button (during API call)
- Success: Checkmark briefly, then revert
- Disabled: Grayed out (no variant or out of stock)

**Click behavior (Critical):**
1. Show loading state (disable button, show spinner)
2. Call API to add item to cart (backend)
3. **Optimistic UI**: Update cart count immediately (before API response)
4. Show success feedback (toast, checkmark, or cart popup)
5. Update cart count in navbar header
6. **DON'T navigate away** - stay on product page
7. Handle errors: restore count if API fails

**Success feedback options:**
- Toast notification: "Added to cart" (top-right)
- Cart popup: Show mini cart with items (see cart-popup.md)
- Checkmark in button briefly, then revert
- All three combined (checkmark + toast or cart popup)

**Error handling:**
```typescript
async function addToCart(variantId, quantity) {
  try {
    // Optimistic update
    updateCartCountUI(+quantity)

    // API call
    // Change this based on the backend integrated
    await fetch(`/store/carts/${cartId}/line-items`, {
      method: 'POST',
      body: JSON.stringify({ variant_id: variantId, quantity })
    })

    // Success feedback
    showToast('Added to cart')
    showCartPopup() // Optional
  } catch (error) {
    // Revert optimistic update
    updateCartCountUI(-quantity)

    // Show error
    if (error.message === 'OUT_OF_STOCK') {
      showError('Sorry, this item is now out of stock')
      updateStockStatus('out_of_stock')
    } else {
      showError('Failed to add to cart. Please try again.')
    }
  }
}
```

**Buy Now button (optional):**
- Skip cart, go directly to checkout
- Useful for: high-value items, single-item stores, decisive customers
- Secondary button below "Add to Cart"
- Text: "Buy Now" or "Buy It Now"
- Add to cart + redirect to checkout in one action

## Product Details Organization

### Decision: Tabs vs Accordion

**Use Tabs (desktop) when:**
- 3-5 distinct sections
- Each section has substantial content
- Users may want to compare sections
- Desktop has screen space
- Examples: Description, Specifications, Shipping, Reviews

**Use Accordion (mobile) always:**
- Saves vertical space
- Users expand what they need
- Standard mobile pattern
- Collapses after reading

**Hybrid approach (recommended):**
- Tabs on desktop (horizontal navigation)
- Accordion on mobile (vertical expansion)
- Same content, different presentation
- Best of both worlds

### Common Sections

**Description:**
- Product overview (2-4 paragraphs)
- Key features (bullet points)
- Use cases
- Materials and craftsmanship

**Specifications:**
- Technical details (table format)
- Dimensions, weight, materials
- Care instructions
- Compatibility information

**Shipping & Returns:**
- Shipping options and costs
- Delivery timeframes
- Return policy (30 days, 60 days)
- Return process
- Link to full policy page

**Reviews:**
- Embedded in tab/accordion
- Or: Separate section below
- Filter by rating, sort by date
- Review submission form

## Related Products Strategy

**Types of recommendations:**

**"You May Also Like" (Similar products):**
- Same category, similar price point
- Algorithm: category match + price range
- Goal: Show alternatives if unsure about current product

**"Frequently Bought Together" (Complementary):**
- Products commonly purchased together
- Algorithm: order history analysis
- Goal: Increase average order value
- Example: Phone + Case + Screen Protector
- Show bundle discount if available

**"Recently Viewed" (Browsing history):**
- User's browsing history (session or logged-in)
- Helps users return to products they liked
- Goal: Reduce decision paralysis

**"Customers Also Viewed":**
- Products viewed by others who viewed this
- Algorithm: co-viewing patterns
- Goal: Discovery and alternatives

### Display Pattern

**Product slider:**
- 4-6 products visible (desktop)
- 2-3 visible (mobile)
- Horizontal scrolling (swipe on mobile)
- Product cards: image, title, price, rating
- Optional: Quick "Add to Cart" on hover

**Placement:**
- Below product details and reviews
- Above footer
- Full-width section
- Clear heading for each type

**Backend integration:**
```typescript
// Fetch recommendations
// Change this based on the backend integrated
const recommendations = await fetch(`/products/${id}/recommendations`)
// Returns: similar, bought_together, recently_viewed
```

## Trust Signals and Conversion

**Essential trust signals:**

**Near Add to Cart:**
- Free shipping badge (if applicable)
- Free returns icon + text
- Secure checkout icon
- Money-back guarantee
- Warranty information (if applicable)

**Below product title:**
- Customer rating and review count (4.8 ★ 324 reviews)
- Link to reviews section
- "Best seller" or "Top rated" badge

**Payment methods:**
- Accepted payment icons (Visa, Mastercard, PayPal, Apple Pay)
- Small icons (40px)
- Below "Add to Cart" or in footer
- Shows payment options available

**For new/unknown brands:**
- Customer testimonials
- "Join 10,000+ happy customers"
- Security badges (if legitimate - don't fake)
- Social proof (Instagram photos, user content)
- Clear contact information

**For high-value products:**
- Detailed specifications
- Professional photography
- Video demonstrations
- Warranty details prominently displayed
- Customer service contact visible

## Mobile Optimization

**Critical mobile patterns:**

**Sticky "Add to Cart" bar:**
- Fixed at bottom of screen
- Always accessible (no scrolling needed)
- Shows: Price + "Add to Cart" button
- Appears after scrolling past fold
- Higher conversion rates

**Image gallery:**
- Full-width swipeable carousel
- Pinch to zoom
- Dot indicators (1/5, 2/5)
- Tap to open full-screen view

**Variant selection:**
- Large touch targets (44-48px)
- Visual swatches easier than dropdowns
- Clear selected state
- Error messages visible

**Accordion for details:**
- Description, Specs, Shipping as accordion
- Starts collapsed (save space)
- User expands what they need
- Clear expand/collapse indicators

**Reviews section:**
- Expandable (start with 2-3 reviews)
- "Show more" button
- Filter by rating
- Star rating distribution chart

## Checklist

**Essential product detail page features:**

- [ ] High-quality product images with zoom
- [ ] Price displayed correctly (Medusa: use value as-is, not divided)
- [ ] Price shows "From $X" when no variant selected (X = minimum variant price)
- [ ] Variant selection required before adding to cart
- [ ] Variant selection updates: price, stock, image
- [ ] Disable unavailable variant options (gray out)
- [ ] Stock availability indicator (in stock, low stock, out of stock)
- [ ] "Only X left" shown when stock is low (<=5)
- [ ] Add to Cart disabled until variant selected
- [ ] Optimistic UI update (cart count updates immediately)
- [ ] Success feedback (toast, cart popup, or checkmark)
- [ ] Stay on product page after adding (don't navigate away)
- [ ] Error handling (out of stock, API failure)
- [ ] Product description and specifications
- [ ] Customer reviews and ratings
- [ ] Related products recommendations (similar, bought together)
- [ ] Trust signals (free shipping, returns, secure checkout)
- [ ] Payment method icons displayed
- [ ] Breadcrumb navigation
- [ ] Mobile: Swipeable image gallery
- [ ] Mobile: Accordion for product details
- [ ] Mobile: Sticky Add to Cart bar (optional but effective)
- [ ] Tabs on desktop, accordion on mobile (hybrid)
- [ ] Fast loading (<2s, optimize images)
- [ ] Keyboard accessible (tab through options, enter to add)
- [ ] ARIA labels on variant selection (role="group", aria-label)
