Retour au catalogue
Product Showcase Comparison
Tableau comparatif cote a cote de 2-3 produits avec features, prix et CTA. Produit recommande mis en avant.
product-showcasecomplex Both Responsive a11y
corporateminimalecommercesaasuniversalgrid
Theme
"use client";
import { motion } from "framer-motion";
import { Check, Minus } from "lucide-react";
interface ComparisonProduct {
id: string;
name: string;
price: string;
imageSrc?: string;
imageAlt?: string;
highlighted?: boolean;
ctaLabel?: string;
ctaUrl?: string;
}
interface ComparisonRow {
label: string;
values: (string | boolean)[];
}
interface ProductShowcaseComparisonProps {
badge?: string;
title?: string;
subtitle?: string;
products?: ComparisonProduct[];
rows?: ComparisonRow[];
}
const EASE = [0.16, 1, 0.3, 1] as const;
export default function ProductShowcaseComparison({
badge = "Comparaison",
title = "Choisissez votre modele",
subtitle = "Comparez nos produits",
products = [],
rows = [],
}: ProductShowcaseComparisonProps) {
if (products.length === 0) return null;
return (
<section
style={{
padding: "var(--section-padding-y-lg) 0",
background: "var(--color-background)",
}}
>
<div
style={{
maxWidth: "var(--container-max-width)",
margin: "0 auto",
padding: "0 var(--container-padding-x)",
}}
>
{/* Header */}
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-80px" }}
transition={{ duration: 0.5, ease: EASE }}
style={{ textAlign: "center", maxWidth: "600px", margin: "0 auto 3rem" }}
>
{badge && (
<span
style={{
display: "inline-block",
fontSize: "0.75rem",
fontWeight: 600,
textTransform: "uppercase",
letterSpacing: "0.1em",
color: "var(--color-accent)",
marginBottom: "0.75rem",
}}
>
{badge}
</span>
)}
<h2
style={{
fontFamily: "var(--font-sans)",
fontSize: "clamp(1.75rem, 3vw, 2.75rem)",
fontWeight: 700,
letterSpacing: "-0.02em",
color: "var(--color-foreground)",
marginBottom: "0.75rem",
}}
>
{title}
</h2>
<p style={{ fontSize: "1rem", lineHeight: 1.6, color: "var(--color-foreground-muted)" }}>
{subtitle}
</p>
</motion.div>
{/* Comparison table */}
<motion.div
initial={{ opacity: 0, y: 24 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: "-40px" }}
transition={{ duration: 0.6, ease: EASE }}
style={{ overflowX: "auto" }}
>
<table
style={{
width: "100%",
borderCollapse: "collapse",
minWidth: "600px",
}}
>
{/* Product headers */}
<thead>
<tr>
<th style={{ width: "30%", padding: "1rem", textAlign: "left" }} />
{products.map((product, pi) => (
<th
key={product.id}
style={{
padding: "1.5rem 1rem",
textAlign: "center",
verticalAlign: "bottom",
borderRadius: product.highlighted ? "var(--radius-lg) var(--radius-lg) 0 0" : undefined,
background: product.highlighted
? "color-mix(in srgb, var(--color-accent) 8%, var(--color-background))"
: undefined,
}}
>
<motion.div
initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: pi * 0.1, duration: 0.4, ease: EASE }}
>
{product.highlighted && (
<span
style={{
display: "inline-block",
fontSize: "0.6875rem",
fontWeight: 600,
textTransform: "uppercase",
letterSpacing: "0.08em",
color: "var(--color-accent)",
marginBottom: "0.75rem",
}}
>
Recommande
</span>
)}
<div
style={{
width: 64,
height: 64,
margin: "0 auto 0.75rem",
borderRadius: "var(--radius-md)",
background: "var(--color-background-alt)",
overflow: "hidden",
}}
>
{product.imageSrc ? (
<img
src={product.imageSrc}
alt={product.imageAlt || product.name}
style={{ width: "100%", height: "100%", objectFit: "contain" }}
/>
) : (
<div
style={{
width: "100%",
height: "100%",
background: `radial-gradient(circle, color-mix(in srgb, var(--color-accent) 10%, transparent), transparent)`,
}}
/>
)}
</div>
<div
style={{
fontWeight: 700,
fontSize: "1rem",
color: "var(--color-foreground)",
}}
>
{product.name}
</div>
<div
style={{
fontSize: "1.125rem",
fontWeight: 700,
color: "var(--color-accent)",
marginTop: "0.25rem",
}}
>
{product.price}
</div>
</motion.div>
</th>
))}
</tr>
</thead>
{/* Comparison rows */}
<tbody>
{rows.map((row, ri) => (
<tr key={ri}>
<td
style={{
padding: "0.875rem 1rem",
fontSize: "0.9375rem",
color: "var(--color-foreground-muted)",
borderBottom: "1px solid var(--color-border)",
}}
>
{row.label}
</td>
{row.values.map((val, vi) => (
<td
key={vi}
style={{
padding: "0.875rem 1rem",
textAlign: "center",
borderBottom: "1px solid var(--color-border)",
background: products[vi]?.highlighted
? "color-mix(in srgb, var(--color-accent) 8%, var(--color-background))"
: undefined,
}}
>
{typeof val === "boolean" ? (
val ? (
<Check style={{ width: 18, height: 18, color: "var(--color-accent)", margin: "0 auto" }} />
) : (
<Minus style={{ width: 18, height: 18, color: "var(--color-foreground-muted)", margin: "0 auto", opacity: 0.4 }} />
)
) : (
<span style={{ fontSize: "0.9375rem", fontWeight: 500, color: "var(--color-foreground)" }}>
{val}
</span>
)}
</td>
))}
</tr>
))}
</tbody>
{/* CTA row */}
<tfoot>
<tr>
<td style={{ padding: "1.5rem 1rem" }} />
{products.map((product) => (
<td
key={product.id}
style={{
padding: "1.5rem 1rem",
textAlign: "center",
borderRadius: product.highlighted ? "0 0 var(--radius-lg) var(--radius-lg)" : undefined,
background: product.highlighted
? "color-mix(in srgb, var(--color-accent) 8%, var(--color-background))"
: undefined,
}}
>
<a
href={product.ctaUrl || "#"}
style={{
display: "inline-block",
padding: "0.75rem 1.5rem",
borderRadius: "var(--radius-full)",
background: product.highlighted ? "var(--color-accent)" : "transparent",
border: product.highlighted ? "none" : "1px solid var(--color-border)",
color: product.highlighted ? "var(--color-foreground)" : "var(--color-foreground-muted)",
fontWeight: 600,
fontSize: "0.875rem",
textDecoration: "none",
}}
>
{product.ctaLabel || "Choisir"}
</a>
</td>
))}
</tr>
</tfoot>
</table>
</motion.div>
</div>
</section>
);
}
Autres variantes product-showcase
Product Showcase 360
complex · both
boldelegant
Product Showcase Carousel
medium · both
minimalelegant
Product Showcase Exploded
complex · both
boldcorporate
Product Showcase Features
medium · both
minimalcorporate
Product Showcase Hero
medium · both
minimalelegant
Product Showcase Scroll
complex · both
minimalcorporate