CardsIntermediateApril 11, 2026
Product Card
Full-bleed product card with background image, overlay logo/price, and a CTA button. Switches from overlay mode (tablet/mobile) to a right-panel info layout (wide desktop). Ready to wire to Shopify Storefront API.
View Full Demo →Preview
Source
demo.jsx
import ProductCardList from "./index.jsx";
import { productCard } from "../demo-data.js";
export default function ProductCardDemo() {
return <ProductCardList {...productCard} />;
}
index.jsx
import styles from "./styles.module.css";
function ProductCard({ backgroundImage, productUrl, verticalLabel, logoText, description, price }) {
return (
<section
className={styles.card}
style={{ backgroundImage: `url(${backgroundImage})` }}
>
{verticalLabel && (
<figure className={styles.labelVertical}>
<img src={verticalLabel} alt="" />
</figure>
)}
<div className={styles.info}>
{logoText && <p className={styles.logoText}>{logoText}</p>}
{description && <p className={styles.description}>{description}</p>}
<span className={styles.price}>{price}</span>
<div className={styles.actions}>
<a href={productUrl || "#"} className={styles.cta}>+ INFO</a>
</div>
</div>
</section>
);
}
export default function ProductCardList({ cards = [] }) {
return (
<div className={styles.list}>
{cards.map((card, i) => (
<ProductCard key={i} {...card} />
))}
</div>
);
}
styles.module.css
.list {
width: 100%;
}
/* ─── Card ─── */
.card {
position: relative;
width: 100%;
height: 100vh;
background-color: #d3d7da;
background-size: contain;
background-position: center;
background-repeat: no-repeat;
border-bottom: 1px solid #fff;
}
/* ─── Vertical label (wide desktop only) ─── */
.labelVertical {
display: none;
}
.labelVertical img {
height: 200px;
width: auto;
}
/* ─── Info — overlay mode (default, all sizes ≤ 1279px) ─── */
.info {
position: absolute;
inset: 0;
padding: 2rem 2.5rem;
display: grid;
grid-template-areas:
"logo price"
"actions actions";
grid-template-rows: 1fr auto;
grid-template-columns: 1fr auto;
align-items: start;
}
.logoText {
grid-area: logo;
font-family: cursive;
font-size: 2rem;
font-weight: normal;
line-height: 1.1;
margin: 0;
}
.description {
display: none;
}
.price {
grid-area: price;
font-size: 0.875rem;
font-weight: 700;
letter-spacing: -0.02em;
text-align: right;
white-space: nowrap;
margin: 0;
}
.actions {
grid-area: actions;
justify-self: end;
align-self: end;
}
.cta {
display: inline-block;
padding: 0.5rem 1.125rem;
border: 1px solid #000;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 700;
letter-spacing: -0.03em;
text-transform: uppercase;
background: transparent;
color: #000;
text-decoration: none;
cursor: pointer;
transition: background 0.15s, color 0.15s;
}
.cta:hover {
background: #000;
color: #fff;
}
/* ─── Tablet / laptop (481px – 1279px) ─── */
@media (max-width: 1279px) {
.card {
height: 560px;
}
}
/* ─── Mobile (≤ 480px) ─── */
@media (max-width: 480px) {
.card {
height: 75vw;
min-height: 260px;
}
.info {
padding: 1.25rem;
}
.logoText {
font-size: 1.125rem;
}
.price {
font-size: 0.75rem;
}
.cta {
background: #000;
color: #fff;
padding: 0.4rem 0.875rem;
font-size: 0.688rem;
}
.cta:hover {
background: #333;
}
}
/* ─── Wide desktop — right panel mode (≥ 1280px) ─── */
@media (min-width: 1280px) {
.card {
background-size: 62% auto;
background-position: left center;
}
.labelVertical {
display: block;
position: absolute;
left: 20px;
top: 50%;
transform: translateY(-50%);
margin: 0;
}
.info {
position: absolute;
inset: auto;
right: 60px;
top: 50%;
transform: translateY(-50%);
max-width: 360px;
padding: 0;
display: flex;
flex-direction: column;
gap: 0.625rem;
}
.logoText {
font-size: 3.5rem;
white-space: normal;
margin: 0;
}
.description {
display: block;
font-size: 0.8125rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.02em;
line-height: 1.3;
margin: 0;
}
.price {
font-size: 1rem;
text-align: left;
white-space: normal;
margin: 0;
}
.actions {
justify-self: auto;
align-self: auto;
width: 100%;
}
.cta {
display: block;
text-align: center;
background: #000;
color: #fff;
padding: 0.75rem 1.5rem;
width: 100%;
box-sizing: border-box;
}
.cta:hover {
background: #333;
color: #fff;
}
}