Product Card
Flexible product cards with multiple layouts, quick-buy variants, and optional image slideshows.
ProductCard is the core shopping primitive in shoppablecn. It covers simple catalog cards, denser comparison layouts, and quick-buy flows that slide the variant picker directly over the card instead of navigating away from the page.
Installation
product-card installs from the shoppablecn registry and automatically pulls
in the shared helpers and quick-options dependency it needs for overlay mode.
npx shadcn@latest add @shoppablecn/product-cardUsage
The minimal setup is a product object and the ProductCard component. The
default rendering is the vertical layout shown below.
Examples
Vertical layout
import { ProductCard } from "@/components/ui/product-card"
import type { Product } from "@/components/ui/types"
export function Example() {
const product = {
id: "art-print-001",
title: "Rocket Launch Art Print",
category: "Wall Art",
images: [
{
src: "/products/rocket-launch-art-print.jpg",
alt: "Rocket Launch Framed Art Print in an oak frame",
},
],
price: { amount: 14900, currency: "USD" },
} satisfies Product
return (
<ProductCard
layout="vertical"
product={product}
variants="none"
/>
)
}Horizontal layout
import { ProductCard } from "@/components/ui/product-card"
import type { Product } from "@/components/ui/types"
export function Example() {
const product = {
id: "pillow-001",
title: "Pixel Heart Accent Pillow",
category: "Decor",
images: [
{
src: "/products/pixel-heart-accent-pillow.jpg",
alt: "Pixel Heart Accent Pillow with a retro rainbow heart graphic",
},
],
price: { amount: 5900, currency: "USD" },
} satisfies Product
return (
<ProductCard
layout="horizontal"
product={product}
variants="none"
/>
)
}Horizontal detailed layout
import { ProductCard } from "@/components/ui/product-card"
import type { Product } from "@/components/ui/types"
export function Example() {
const product = {
id: "controller-001",
title: "Retro Wireless Controller",
category: "Gaming",
description:
"A rechargeable retro-style controller with tactile face buttons and a familiar classic layout.",
images: [
{
src: "/products/retro-wireless-controller.jpg",
alt: "Retro Wireless Controller with colorful face buttons",
},
],
price: { amount: 5900, currency: "USD" },
} satisfies Product
return (
<ProductCard
layout="horizontal-detailed"
product={product}
variants="none"
/>
)
}With badge
import { ProductCard } from "@/components/ui/product-card"
import type { Product } from "@/components/ui/types"
export function Example() {
const product = {
id: "pillow-001",
title: "Pixel Heart Accent Pillow",
images: [
{
src: "/products/pixel-heart-accent-pillow.jpg",
alt: "Pixel Heart Accent Pillow with a retro rainbow heart graphic",
},
],
price: { amount: 5900, currency: "USD" },
badge: { label: "New", variant: "new" },
} satisfies Product
return <ProductCard product={product} showRating={false} />
}With rating
import { ProductCard } from "@/components/ui/product-card"
import type { Product } from "@/components/ui/types"
export function Example() {
const product = {
id: "art-print-001",
title: "Rocket Launch Art Print",
images: [
{
src: "/products/rocket-launch-art-print.jpg",
alt: "Rocket Launch Framed Art Print in an oak frame",
},
],
price: { amount: 14900, currency: "USD" },
rating: { value: 3.5, count: 41 },
} satisfies Product
return <ProductCard product={product} showRating />
}With compare-at price
import { ProductCard } from "@/components/ui/product-card"
import type { Product } from "@/components/ui/types"
export function Example() {
const product = {
id: "controller-001",
title: "Retro Wireless Controller",
images: [
{
src: "/products/retro-wireless-controller.jpg",
alt: "Retro Wireless Controller with colorful face buttons",
},
],
price: { amount: 5900, compareAt: 7900, currency: "USD" },
} satisfies Product
return <ProductCard product={product} showRating={false} />
}With overlay variants
import { ProductCard } from "@/components/ui/product-card"
import type { Product, ProductVariant } from "@/components/ui/types"
export function Example() {
const variants = [
{
id: "upholstery",
name: "Upholstery",
type: "swatch",
required: true,
options: [
{ label: "Oat", value: "oat", swatch: "#e6ddd1" },
{ label: "Pebble", value: "pebble", swatch: "#b8b0a4" },
{ label: "Charcoal", value: "charcoal", swatch: "#44403c" },
],
},
{
id: "delivery",
name: "Delivery",
type: "radio",
required: true,
options: [
{ label: "Standard", value: "standard" },
{ label: "White Glove", value: "white-glove" },
],
},
] satisfies ProductVariant[]
const product = {
id: "chair-001",
title: "Harbor Lounge Chair",
images: [
{
src: "/products/harbor-lounge-chair.jpg",
alt: "Harbor Lounge Chair in oat boucle",
},
],
price: { amount: 64900, currency: "USD" },
variants,
} satisfies Product
return (
<ProductCard
onAddToCart={() => {}}
product={product}
variants="overlay"
/>
)
}Image slideshow
import { ProductCard } from "@/components/ui/product-card"
import type { Product } from "@/components/ui/types"
export function Example() {
const product = {
id: "room-set-001",
title: "Gaming Lounge Styling Set",
images: [
{
src: "/products/harbor-lounge-chair.jpg",
alt: "Harbor Lounge Chair in ivory upholstery",
},
{
src: "/products/pixel-heart-accent-pillow.jpg",
alt: "Pixel Heart Accent Pillow with a retro rainbow heart graphic",
},
{
src: "/products/retro-wireless-controller.jpg",
alt: "Retro Wireless Controller with colorful face buttons",
},
{
src: "/products/rocket-launch-art-print.jpg",
alt: "Rocket Launch Framed Art Print in an oak frame",
},
],
price: { amount: 72900, currency: "USD" },
} satisfies Product
return <ProductCard product={product} />
}Slideshow control variants
import { ProductCard } from "@/components/ui/product-card"
import type { Product } from "@/components/ui/types"
export function Example() {
const product = {
id: "room-set-001",
title: "Gaming Lounge Styling Set",
images: [
{
src: "/products/harbor-lounge-chair.jpg",
alt: "Harbor Lounge Chair in ivory upholstery",
},
{
src: "/products/pixel-heart-accent-pillow.jpg",
alt: "Pixel Heart Accent Pillow with a retro rainbow heart graphic",
},
{
src: "/products/retro-wireless-controller.jpg",
alt: "Retro Wireless Controller with colorful face buttons",
},
{
src: "/products/rocket-launch-art-print.jpg",
alt: "Rocket Launch Framed Art Print in an oak frame",
},
],
price: { amount: 72900, currency: "USD" },
} satisfies Product
function rotateImages(startIndex: number): Product {
return {
...product,
id: `${product.id}-${startIndex}`,
images: [
...product.images.slice(startIndex),
...product.images.slice(0, startIndex),
],
}
}
const slideshowProducts = [0, 1, 2, 3].map(rotateImages)
return (
<>
<ProductCard product={slideshowProducts[0]} slideshowControls="both" />
<ProductCard product={slideshowProducts[1]} slideshowControls="arrows" />
<ProductCard product={slideshowProducts[2]} slideshowControls="dots" />
<ProductCard product={slideshowProducts[3]} slideshowControls="none" />
</>
)
}Props
Prop
Type
Accessibility
- Keyboard navigation works for slideshow controls, action buttons, and overlay interactions.
- Icon-only actions expose accessible labels.
- Overlay mode manages focus and respects
prefers-reduced-motion. - Slide navigation remains available with arrow keys when the image region is focused.
Related
- QuickOptions powers the overlay variant flow.
- HotspotImage can render ProductCard inside shoppable scene pins.



