Skill 01 · Storefront Best Practices
Subchapter 1.18
reference/layouts/cart.mdMarkdown10 KBView on GitHub
The cart page displays all items a customer has added to their shopping cart. Purpose: Review items, modify cart, apply promotions, proceed to checkout. Critical conversion point.
⚠️ CRITICAL: Always display variant details (size, color, material, etc.) for each cart item, not just product titles.
Most common:
Single column (stacked):
CRITICAL: Always display variant details for each cart item.
Products with variants (size, color, material, style, etc.) must show the selected variant options. Without this, customers can’t confirm they have the correct items in their cart.
Essential information per item:
Layout: Horizontal card (image left, details right), clear visual separation between items, adequate spacing (16-24px).
Why variant details are critical:
Medusa pricing (CRITICAL): Medusa stores prices as-is (not in cents). Display prices directly without dividing by 100. Example: If Medusa returns 49.99, display $49.99 (not $0.4999). Different from Stripe which stores prices in cents.
Sale prices: Show original price (strikethrough) and sale price prominently if on sale.
Line total: Total for item (price × quantity), bold or larger font, update dynamically when quantity changes.
Standard +/- buttons with number display:
[-] [2] [+]Behavior:
Changes apply immediately, no “Update Cart” button needed. Better UX, less friction. Show brief loading indicator. Update order summary automatically.
Error handling: “Only X available” if exceeds stock, reset to max available quantity, show error message near item.
Position: Right column on desktop (sticky), below cart items on mobile, fixed width (300-400px desktop).
Line items:
Subtotal (3 items): $149.97
Shipping: $9.99
Tax: $12.00
─────────────────────
Total: $171.96Subtotal: Sum of all cart items with item count.
Shipping: Estimated shipping cost, or “Calculated at checkout” (if address needed), or “Free shipping” (if applicable). Show free shipping threshold progress (see promotions.md).
Tax: Estimated tax or “Calculated at checkout” (if address needed).
Total: Grand total (bold, larger font), most prominent number.
If discounts applied:
Layout: Label (“Promo code” or “Discount code”), text input (200-280px desktop, full-width mobile), “Apply” button inline or stacked (mobile). Positioned below cart items or in order summary.
Auto-uppercase: On submit (codes usually uppercase).
Expandable pattern (optional): “Have a promo code?” link that expands to show input. Saves vertical space.
Success:
Error:
See also: promotions.md for detailed promo code patterns.
Prominence: Large, full-width button, brand primary color (high contrast), 48-56px height (easy to tap). Text: “Proceed to Checkout” or “Checkout”. Icon optional (lock or arrow).
Position: Bottom of order summary (desktop), fixed at bottom of screen (mobile, optional), always visible during scroll.
States: Default enabled, hover with slight color change, loading with spinner, disabled if cart empty or error.
Security Indicators (optional): Lock icon with “Secure Checkout”, payment badges (Visa, Mastercard, PayPal), “SSL Encrypted” message near button.
When cart is empty:
Additional elements:
Fetch from ecommerce backend: Cart stored in backend (persistent), fetch on page load, sync with backend on changes.
When to fetch:
Client-side cart state: Store cart data in global state (React Context), keep cart ID in localStorage, update state after API responses, share cart state across components (page, popup, header badge).
Cart ID persistence:
localStorage.setItem('cart_id', cartId)Send cart ID with every cart API request, create new cart if ID doesn’t exist, clear cart ID on checkout completion.
Recommended for efficient caching and revalidation:
Benefits: Built-in caching with automatic revalidation, optimistic updates support, automatic refetching on focus/reconnect, loading and error states handled, query invalidation for cart updates.
Configuration:
Use useQuery for fetching cart data, set staleTime to 30-60 seconds, use queryClient.invalidateQueries(['cart']) after updates.
See also: connecting-to-backend.md for detailed backend integration patterns.
Use @medusajs/medusa-js SDK:
/store/carts, /store/carts/{id}/store/carts/{id}/line-items/store/carts/{id}/line-items/{lineId}/store/carts/{id}/line-items/{lineId}/store/carts/{id}/promotionsResponse data: Cart ID, items (product details, variants, quantities), subtotal, tax, shipping, total, applied discounts, item availability status.
Error handling: Network errors (show retry option), invalid cart ID (create new cart), out of stock (show error, prevent adding), API errors (user-friendly message).
Structure: Full-width cart items (stacked), simplified item cards, order summary below items, sticky checkout button at bottom.
Cart item cards: Smaller product images (60-80px), truncated product titles (1-2 lines), essential info only, quantity selector (smaller, 36-40px), remove button visible.
Bottom sticky bar: Fixed at bottom of screen, total amount visible, “Checkout” button (full-width), appears after scrolling (optional), always accessible.
Design:
[Total: $171.96] [Checkout]Touch-friendly: 44px minimum touch targets, adequate spacing between buttons, large remove buttons (40px).
Essential elements: