asafefe.dev

Product Card

New

A storefront card. Swatches crossfade between product photos, sizes can change the price, a wishlist heart pops and Add to bag confirms with a check.

motionlucide-reactutilsbadgebuttoncardfield@soultel/rolling-number@soultel/use-reduced-motion-safe
Graphite over-ear headphones on a light grey surface
New
Soundline
Studio Over-Ear
4.8 (1204)
$449$549
ColorGraphite
Edition

Installation

$ pnpm dlx shadcn@latest add @soultel/product-card

No namespace set up? npx shadcn@latest add https://asafefe.dev/r/product-card.json works too.

Manual installation
  1. 1. Install the dependencies.
    terminal1 lines
    npm install motion lucide-react
  2. 2. Copy the source from the Code tab into components/product-card.tsx.

Usage

example.tsx22 lines
import { ProductCard, type Product } from "@/components/product-card"

const sneaker: Product = {
  id: "runner-01",
  brand: "Northside",
  name: "Daybreak Runner",
  price: 128,
  compareAt: 150,
  rating: 4.7,
  reviews: 212,
  badge: "New",
  colors: [
    { name: "Ember", value: "#e2412f", image: "/products/runner-ember.jpg" },
    { name: "Ink", value: "#2b2a28", image: "/products/runner-ink.jpg" },
    { name: "Sage", value: "#7f9c7a", image: "/products/runner-sage.jpg", imageAlt: "Daybreak Runner in sage, side view" },
  ],
  sizes: [{ label: "7" }, { label: "8" }, { label: "9" }, { label: "10", available: false }, { label: "11" }],
}

export function RunnerCard() {
  return <ProductCard product={sneaker} onAddToCart={(selection) => addToCart(selection)} />
}

Props

PropTypeDefaultDescription
productProduct-id, name, price, colors { name, value, image, imageAlt }[], and optional brand, compareAt, rating, reviews, sizes { label, priceDelta, available }[], badge, and a fallback image and imageAlt. Photos render with a plain <img>, so any URL works without image config.
color / defaultColorstring-Controlled or initial color name. Defaults to the first color.
onColorChange(color: ProductColor) => void-Called when a swatch is picked.
size / defaultSizestring-Controlled or initial size label. Defaults to the first available size.
onSizeChange(size: ProductSize) => void-Called when a size is picked.
wishlisted / defaultWishlistedbooleanfalseControlled or initial wishlist state.
onWishlistChange(wishlisted: boolean) => void-Called when the heart is toggled.
onAddToCart(selection: ProductSelection) => void-Called with productId, color, size, price and quantity when Add to bag is pressed.
currencystring"$"Symbol shown before prices.
sizeLabelstring"Size"Heading above the size picker, e.g. "Edition".