Skip to content
AApexCommerce Design System All pages
Phase 01 — Component library

Product card system

The most repeated object in the product — it appears on home, shop, category, search, wishlist, cart recommendations, admin and reports. One component, eight designed states, zero variants invented per page.

1

Anatomy

Every element earns its place by answering a purchase question: what is it, who makes it, do people like it, what does it cost, can I get it, what can I do right now.

  1. 1Media 4:5 — portrait crops carry apparel better than squares; zooms 4% on hover only (reduced-motion safe: none).
  2. 2Badges top-left, max two — sale first, then new/best-seller. Stock is never a badge here; it lives in the footer.
  3. 3Wishlist + quick view top-right — revealed on hover/focus on desktop, always visible on touch. 36px targets.
  4. 4Quick-add bar — slides up inside the media on hover (desktop only). Touch users add from the PDP; the card never steals their tap.
  5. 5Brand overline — 11px uppercase muted; doubles as category on brand-less goods.
  6. 6Name, 2-line clamp — the whole media + name are one link; the card is not a link to avoid nested-interactive traps.
  7. 7Rating — stars ochre, count muted, value in ink. Hidden entirely until 3+ reviews (no fake 5.0).
  8. 8Swatches — first four colours + overflow count; selecting a swatch swaps the media tone client-side.
  9. 9Price stack — current, was (struck), save. Red only when discounted; unit price for grocery/SKU goods.
  10. 10Stock line — dot + text. Low stock pulses once per session, never loops.
2

The eight states

Each state below is a separate, named class on the same component. Wishlist and added-to-cart are interactive here — click the heart or Add to cart and watch the choreography.

1 · Normal .apx-product
2 · Hover :hover / :focus-within
3 · Loading .apx-skeleton
4 · Out of stock .is-oos
5 · Sale
6 · Low stock
7 · Added to cart .is-added
8 · Wishlist active .is-wishlisted
Try it live
Every card on this page is wired: hearts toggle wishlist with a pop + toast, and any “Add to cart” runs the 520ms loading → success flash → header badge bump choreography from page 02.
3

Interaction choreography

TriggerResponseTiming
Hover cardLift 3px + md shadow + media zoom 4% + action buttons reveal + quick-add slide-up200ms ease-out, simultaneous
Tap heartIcon swaps to filled, pop scale 1→1.28→1, toast “Added to wishlist”, header heart count +1340ms spring
Add to cartButton spinner 520ms → card flash 1.1s → badge bump → toast with View cart action520 / 1100 / 380ms
Swatch tapMedia tone cross-fades, swatch ring moves; price/stock may update if variant differs200ms
Quick viewBottom sheet <576px, centred 640px modal above; opens with scale-in, focus to Close320ms
Out-of-stock tapMedia stays grayscale; “Notify me” opens email capture modal; no dead links, ever
4

Sizing & grid behaviour

The card is fluid: it never has a fixed width. Columns come from the grid — 2-up on phones (thumb-sized but tap-friendly), 3-up tablet, 4-up desktop, and 4-across rails that snap-scroll below 992px.

Mobile · 2 columns

Mobile card drops rating + swatches to keep the price above the fold; quick-add hidden, hearts stay.

Tablet · 3 columns
Desktop · 4 columns
Sonos
$299
C. Road
$249
Breville
$999
Muuto
$189
5

Implementation specs (Next.js + Bootstrap)

ElementClass / tokenNotes
Shell.apx-productflex column, height:100% so grid rows align
Media.apx-media .apx-media-4x5aspect-ratio, object-fit cover, lazy below fold
Name link.apx-product-name astretched over media via one <a>, no nested links
Wishlist.apx-product-action[data-wishlist]aria-pressed, heart ↔ heart-fill
Price.apx-price-current / -was / -savetabular-nums; <del> semantics on was-price
Stock.apx-stock-in|-low|-out|-backorderdot + text, never colour alone
Skeleton.apx-skeleton variantssame box metrics as the real card → zero CLS