CardsbeginnerApril 11, 2026
Product Card Grid
3-column product grid with square image cards and a bottom info strip showing title and price. Collapses to 2 columns on mobile. Ready to wire to Shopify Storefront API or any product collection endpoint.
View Full Demo →Preview
Source
demo.jsx
import ProductCardGrid from "./index.jsx";
import { productCardGrid } from "../demo-data.js";
export default function ProductCardGridDemo() {
return <ProductCardGrid {...productCardGrid} />;
}
index.jsx
import styles from "./styles.module.css";
function ProductItem({ image, title, price, href }) {
return (
<a href={href || "#"} className={styles.card}>
<div
className={styles.imageArea}
style={{ backgroundImage: `url(${image})` }}
/>
<div className={styles.info}>
<span className={styles.title}>{title}</span>
<span className={styles.price}>{price}</span>
</div>
</a>
);
}
export default function ProductCardGrid({ items = [] }) {
return (
<div className={styles.grid}>
{items.map((item, i) => (
<ProductItem key={i} {...item} />
))}
</div>
);
}
styles.module.css
/* ─── Grid ─── */
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1px;
background: #fff;
}
/* ─── Card ─── */
.card {
display: flex;
flex-direction: column;
text-decoration: none;
color: inherit;
background: #d3d7da;
cursor: pointer;
}
/* ─── Image area ─── */
.imageArea {
aspect-ratio: 1;
background-color: #d3d7da;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
}
/* ─── Info strip ─── */
.info {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 1rem;
padding: 0.75rem 1rem;
}
.title {
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.02em;
line-height: 1.35;
}
.price {
font-size: 0.75rem;
font-weight: 700;
letter-spacing: -0.02em;
text-align: right;
white-space: nowrap;
flex-shrink: 0;
}
/* ─── Mobile (≤ 640px) ─── */
@media (max-width: 640px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
.info {
flex-direction: column;
gap: 0.25rem;
align-items: flex-start;
}
.price {
white-space: normal;
text-align: left;
}
}