SectionsSimpleApril 21, 2026
Product Feature Grid
A 3-column product feature section with centered badge overlays on product images, serif heading with italic emphasis, description, and a CTA button. Cream background, editorial styling. Shopify CMS-ready.
View Full Demo →Preview
Gifts & Favors
The gift they'll actually use.
Baby showers. Birthdays. Just because. Our minis are the perfect way to share better skincare — or try a new scent yourself.
Source
demo.jsx
import ProductFeatureGrid from "./index.jsx";
import { productFeatureGrid } from "../demo-data.js";
export default function ProductFeatureGridDemo() {
return <ProductFeatureGrid {...productFeatureGrid} />;
}
index.jsx
"use client";
import styles from "./styles.module.css";
/*
── Shopify CMS Integration ──
To pull products from Shopify instead of static data:
// Storefront API query for a specific collection:
// const COLLECTION_QUERY = `
// query ($handle: String!) {
// collection(handle: $handle) {
// products(first: 6) {
// nodes {
// id
// title
// handle
// priceRange {
// minVariantPrice { amount currencyCode }
// }
// featuredImage {
// url
// altText
// }
// metafield(namespace: "custom", key: "badge_label") {
// value
// }
// metafield(namespace: "custom", key: "details_line") {
// value
// }
// }
// }
// }
// }
// `;
//
// Map response to items:
// const items = data.collection.products.nodes.map(product => ({
// title: product.title,
// href: `/products/${product.handle}`,
// image: product.featuredImage?.url,
// badge: product.metafield?.value || product.title,
// details: product.metafield?.value ||
// `${product.priceRange.minVariantPrice.currencyCode}$${product.priceRange.minVariantPrice.amount}`,
// }));
*/
export default function ProductFeatureGrid({
label,
heading,
headingEmphasis,
description,
items = [],
ctaLabel,
ctaHref = "#",
}) {
const renderHeading = () => {
if (!headingEmphasis || !heading) return heading;
const parts = heading.split(headingEmphasis);
if (parts.length < 2) return heading;
return (
<>
{parts[0]}<em>{headingEmphasis}</em>{parts[1]}
</>
);
};
return (
<section className={styles.section}>
{label && <p className={styles.label}>{label}</p>}
{heading && <h2 className={styles.heading}>{renderHeading()}</h2>}
{description && <p className={styles.description}>{description}</p>}
<div className={styles.grid}>
{items.map((item, i) => (
<a key={i} href={item.href || "#"} className={styles.card}>
<div className={styles.imageWrap}>
<img src={item.image} alt={item.title} className={styles.image} />
{item.badge && (
<span className={styles.badge}>{item.badge}</span>
)}
</div>
<h3 className={styles.title}>{item.title}</h3>
{item.details && <p className={styles.details}>{item.details}</p>}
</a>
))}
</div>
{ctaLabel && (
<div className={styles.ctaWrap}>
<a href={ctaHref} className={styles.cta}>{ctaLabel}</a>
</div>
)}
</section>
);
}
styles.module.css
.section {
width: 100%;
background: #f1ebe7;
padding: 6rem 2rem 8rem;
text-align: center;
}
/* ---- Label ---- */
.label {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.2em;
color: #555;
margin-bottom: 1.5rem;
}
/* ---- Heading ---- */
.heading {
font-size: clamp(1.75rem, 3.5vw, 3rem);
font-weight: 400;
line-height: 1.2;
color: #1a1a1a;
margin-bottom: 1.25rem;
font-family: Georgia, "Times New Roman", serif;
}
.heading em {
font-style: italic;
}
/* ---- Description ---- */
.description {
font-size: clamp(14px, 1.1vw, 16px);
line-height: 1.6;
color: #555;
max-width: 50ch;
margin: 0 auto 3rem;
}
/* ---- Grid ---- */
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
max-width: 1100px;
margin: 0 auto;
}
/* ---- Card ---- */
.card {
text-decoration: none;
color: inherit;
text-align: left;
display: block;
}
/* ---- Image ---- */
.imageWrap {
position: relative;
width: 100%;
aspect-ratio: 4 / 5;
overflow: hidden;
border-radius: 0.25rem;
margin-bottom: 1rem;
}
.image {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: transform 0.5s ease;
}
.card:hover .image {
transform: scale(1.03);
}
/* ---- Badge ---- */
.badge {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 0.6rem;
text-transform: uppercase;
letter-spacing: 0.14em;
color: #1a1a1a;
background: rgba(255, 255, 255, 0.85);
padding: 0.5rem 1rem;
white-space: nowrap;
pointer-events: none;
}
/* ---- Title ---- */
.title {
font-family: Georgia, "Times New Roman", serif;
font-size: clamp(1rem, 1.4vw, 1.25rem);
font-weight: 400;
line-height: 1.3;
color: #1a1a1a;
margin-bottom: 0.25rem;
}
/* ---- Details ---- */
.details {
font-size: 0.75rem;
letter-spacing: 0.02em;
color: #777;
}
/* ---- CTA ---- */
.ctaWrap {
margin-top: 3rem;
text-align: center;
}
.cta {
display: inline-block;
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.14em;
color: #fff;
background: #2a2a2a;
padding: 1.1rem 2.5rem;
text-decoration: none;
transition: background 0.3s ease;
}
.cta:hover {
background: #000;
}
/* ---- Desktop ---- */
@media (min-width: 768px) {
.grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* ---- Mobile ---- */
@media (max-width: 767px) {
.section {
padding: 4rem 1rem 5rem;
}
.grid {
gap: 2.5rem;
}
}


