Wedding — Testimonials
An editorial romantic testimonials section for wedding planners and venues: elegant blush-and-gold quote cards pairing a couple portrait, wedding date and venue with a heartfelt review. A soft carousel glides between stories with prev, next and dot controls, autoplay that pauses on hover, keyboard arrows, swipe on touch, and a live star-rating summary. Thin gold rules and generous Cormorant serifs keep it airy, accessible and calm.
MCP
Code
:root {
--blush: #e8b7b0;
--blush-50: #fbeeec;
--rose: #c98a86;
--gold: #c9a24b;
--gold-d: #a8862f;
--ink: #3a2b2b;
--ink-2: #6b5555;
--muted: #9a8585;
--bg: #fbf7f4;
--cream: #f4ece4;
--white: #fff;
--line: rgba(58, 43, 43, 0.12);
--r-sm: 8px;
--r-md: 16px;
--r-lg: 26px;
--serif: "Cormorant Garamond", Georgia, serif;
--sans: "Inter", system-ui, -apple-system, sans-serif;
--shadow: 0 24px 60px -28px rgba(58, 43, 43, 0.35);
}
* {
box-sizing: border-box;
}
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
font-family: var(--sans);
line-height: 1.5;
color: var(--ink);
background:
radial-gradient(1200px 500px at 50% -10%, var(--blush-50), transparent 70%),
var(--bg);
min-height: 100vh;
}
.wrap {
max-width: 940px;
margin: 0 auto;
padding: clamp(32px, 6vw, 72px) 20px;
}
/* ---------- Header ---------- */
.head {
text-align: center;
margin-bottom: 34px;
}
.eyebrow {
display: flex;
align-items: center;
justify-content: center;
gap: 14px;
margin: 0 0 10px;
font-size: 0.74rem;
letter-spacing: 0.28em;
text-transform: uppercase;
font-weight: 600;
color: var(--gold-d);
}
.eyebrow .rule {
width: clamp(28px, 8vw, 64px);
height: 1px;
background: linear-gradient(90deg, transparent, var(--gold), transparent);
}
h1 {
font-family: var(--serif);
font-weight: 500;
font-size: clamp(2.1rem, 6vw, 3.3rem);
line-height: 1.06;
margin: 0 0 12px;
color: var(--ink);
}
.sub {
max-width: 30rem;
margin: 0 auto;
color: var(--ink-2);
font-size: 1rem;
}
/* ---------- Stats ---------- */
.stats {
display: flex;
justify-content: center;
gap: clamp(18px, 6vw, 52px);
margin: 28px 0 0;
padding: 18px 0 0;
border-top: 1px solid var(--line);
}
.stat {
text-align: center;
}
.stat dt {
font-size: 0.7rem;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 4px;
}
.stat dd {
margin: 0;
font-family: var(--serif);
font-weight: 600;
font-size: 1.85rem;
color: var(--ink);
line-height: 1;
}
.stat-star {
color: var(--gold);
font-size: 1.1rem;
}
.pct {
font-size: 1rem;
color: var(--gold-d);
}
/* ---------- Carousel ---------- */
.carousel {
position: relative;
display: flex;
align-items: center;
gap: 8px;
}
.viewport {
overflow: hidden;
flex: 1;
border-radius: var(--r-lg);
}
.track {
display: flex;
margin: 0;
padding: 0;
list-style: none;
transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
will-change: transform;
}
.slide {
min-width: 100%;
padding: 10px;
}
/* ---------- Card ---------- */
.card {
display: grid;
grid-template-columns: 200px 1fr;
gap: clamp(20px, 4vw, 40px);
align-items: center;
background: var(--white);
border: 1px solid var(--line);
border-radius: var(--r-lg);
padding: clamp(20px, 4vw, 36px);
box-shadow: var(--shadow);
position: relative;
}
.card::after {
content: "";
position: absolute;
inset: 7px;
border: 1px solid rgba(201, 162, 75, 0.28);
border-radius: calc(var(--r-lg) - 7px);
pointer-events: none;
}
.portrait {
margin: 0;
position: relative;
aspect-ratio: 4 / 5;
border-radius: var(--r-md);
overflow: hidden;
box-shadow: 0 10px 30px -14px rgba(58, 43, 43, 0.5);
}
.portrait::before {
content: "";
position: absolute;
inset: 0;
border: 3px solid var(--white);
border-radius: var(--r-md);
box-shadow: inset 0 0 0 1px rgba(201, 162, 75, 0.4);
z-index: 2;
pointer-events: none;
}
.portrait img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transition: transform 0.8s ease;
}
.card:hover .portrait img {
transform: scale(1.05);
}
.body {
position: relative;
min-width: 0;
}
.quote-mark {
position: absolute;
top: -34px;
left: -6px;
font-family: var(--serif);
font-size: 5rem;
line-height: 1;
color: var(--blush);
opacity: 0.7;
}
.stars {
color: var(--gold);
letter-spacing: 3px;
font-size: 1rem;
margin-bottom: 12px;
}
.star-empty {
color: rgba(201, 162, 75, 0.28);
}
blockquote {
margin: 0 0 18px;
font-family: var(--serif);
font-weight: 500;
font-size: clamp(1.25rem, 2.6vw, 1.6rem);
line-height: 1.35;
color: var(--ink);
}
.who {
display: flex;
flex-direction: column;
gap: 3px;
padding-top: 14px;
border-top: 1px solid var(--line);
}
.names {
font-family: var(--serif);
font-weight: 600;
font-size: 1.25rem;
color: var(--rose);
}
.meta {
font-size: 0.82rem;
color: var(--muted);
letter-spacing: 0.02em;
}
.share {
margin-top: 16px;
appearance: none;
border: 1px solid var(--gold);
background: transparent;
color: var(--gold-d);
font-family: var(--sans);
font-size: 0.78rem;
font-weight: 600;
letter-spacing: 0.06em;
padding: 8px 16px;
border-radius: 999px;
cursor: pointer;
transition: background 0.2s ease, color 0.2s ease, transform 0.15s ease;
}
.share:hover {
background: var(--gold);
color: var(--white);
}
.share:active {
transform: translateY(1px);
}
/* ---------- Nav buttons ---------- */
.nav {
flex: 0 0 auto;
width: 46px;
height: 46px;
display: grid;
place-items: center;
border-radius: 50%;
border: 1px solid var(--line);
background: var(--white);
color: var(--rose);
cursor: pointer;
box-shadow: 0 8px 22px -12px rgba(58, 43, 43, 0.4);
transition: background 0.2s ease, color 0.2s ease, transform 0.15s ease, border-color 0.2s ease;
z-index: 3;
}
.nav svg {
width: 20px;
height: 20px;
fill: none;
stroke: currentColor;
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
}
.nav:hover {
background: var(--rose);
color: var(--white);
border-color: var(--rose);
transform: scale(1.06);
}
.nav:active {
transform: scale(0.96);
}
/* ---------- Dots ---------- */
.dots {
display: flex;
justify-content: center;
gap: 10px;
margin-top: 22px;
}
.dot {
width: 9px;
height: 9px;
padding: 0;
border: none;
border-radius: 50%;
background: rgba(201, 138, 134, 0.32);
cursor: pointer;
transition: transform 0.25s ease, background 0.25s ease, width 0.25s ease;
}
.dot:hover {
background: var(--rose);
}
.dot.active {
width: 26px;
border-radius: 999px;
background: linear-gradient(90deg, var(--rose), var(--gold));
}
/* ---------- Focus ---------- */
:where(button):focus-visible {
outline: 2px solid var(--gold-d);
outline-offset: 3px;
}
/* ---------- Toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 26px;
transform: translate(-50%, 18px);
background: var(--ink);
color: var(--cream);
padding: 12px 20px;
border-radius: 999px;
font-size: 0.85rem;
font-weight: 500;
letter-spacing: 0.02em;
box-shadow: 0 16px 40px -16px rgba(58, 43, 43, 0.6);
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease, transform 0.3s ease;
z-index: 40;
}
.toast::before {
content: "♥ ";
color: var(--blush);
}
.toast.show {
opacity: 1;
transform: translate(-50%, 0);
}
/* ---------- Responsive ---------- */
@media (max-width: 720px) {
.card {
grid-template-columns: 1fr;
text-align: center;
}
.portrait {
max-width: 220px;
margin: 0 auto;
}
.quote-mark {
position: static;
display: block;
margin-bottom: -18px;
}
.who {
align-items: center;
}
.stars {
display: inline-block;
}
}
@media (max-width: 520px) {
.wrap {
padding: 30px 14px 56px;
}
.stats {
gap: 16px;
flex-wrap: wrap;
}
.stat dd {
font-size: 1.5rem;
}
.nav {
width: 40px;
height: 40px;
}
.carousel {
gap: 2px;
}
.slide {
padding: 6px 2px;
}
.card {
padding: 22px 18px;
}
blockquote {
font-size: 1.2rem;
}
}
@media (prefers-reduced-motion: reduce) {
.track,
.portrait img,
.nav,
.dot,
.toast {
transition: none;
}
}(function () {
"use strict";
var track = document.getElementById("track");
var slides = Array.prototype.slice.call(track.querySelectorAll(".slide"));
var prevBtn = document.getElementById("prevBtn");
var nextBtn = document.getElementById("nextBtn");
var dotsWrap = document.getElementById("dots");
var carousel = document.getElementById("carousel");
var toastEl = document.getElementById("toast");
var index = 0;
var count = slides.length;
var AUTOPLAY_MS = 6000;
var timer = null;
var paused = false;
/* ---------- Dots ---------- */
var dots = slides.map(function (_, i) {
var b = document.createElement("button");
b.className = "dot";
b.type = "button";
b.setAttribute("role", "tab");
b.setAttribute("aria-label", "Story " + (i + 1) + " of " + count);
b.addEventListener("click", function () {
goTo(i, true);
});
dotsWrap.appendChild(b);
return b;
});
/* ---------- Core ---------- */
function render() {
track.style.transform = "translateX(" + -index * 100 + "%)";
dots.forEach(function (d, i) {
var active = i === index;
d.classList.toggle("active", active);
d.setAttribute("aria-selected", active ? "true" : "false");
});
slides.forEach(function (s, i) {
s.setAttribute("aria-hidden", i === index ? "false" : "true");
});
}
function goTo(i, userInitiated) {
index = (i + count) % count;
render();
if (userInitiated) restart();
}
function next(user) {
goTo(index + 1, user);
}
function prev(user) {
goTo(index - 1, user);
}
/* ---------- Autoplay ---------- */
function start() {
stop();
var reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (reduce) return;
timer = window.setInterval(function () {
if (!paused) next(false);
}, AUTOPLAY_MS);
}
function stop() {
if (timer) {
window.clearInterval(timer);
timer = null;
}
}
function restart() {
start();
}
/* ---------- Controls ---------- */
nextBtn.addEventListener("click", function () {
next(true);
});
prevBtn.addEventListener("click", function () {
prev(true);
});
carousel.addEventListener("mouseenter", function () {
paused = true;
});
carousel.addEventListener("mouseleave", function () {
paused = false;
});
carousel.addEventListener("focusin", function () {
paused = true;
});
carousel.addEventListener("focusout", function () {
paused = false;
});
/* ---------- Keyboard ---------- */
document.addEventListener("keydown", function (e) {
if (e.key === "ArrowRight") {
next(true);
} else if (e.key === "ArrowLeft") {
prev(true);
}
});
/* ---------- Touch / swipe ---------- */
var startX = 0;
var deltaX = 0;
var dragging = false;
carousel.addEventListener(
"touchstart",
function (e) {
startX = e.touches[0].clientX;
deltaX = 0;
dragging = true;
paused = true;
},
{ passive: true }
);
carousel.addEventListener(
"touchmove",
function (e) {
if (!dragging) return;
deltaX = e.touches[0].clientX - startX;
},
{ passive: true }
);
carousel.addEventListener("touchend", function () {
if (!dragging) return;
dragging = false;
paused = false;
if (Math.abs(deltaX) > 45) {
if (deltaX < 0) next(true);
else prev(true);
}
});
/* ---------- Share / toast ---------- */
var toastTimer = null;
function toast(msg) {
toastEl.textContent = msg;
toastEl.classList.add("show");
if (toastTimer) window.clearTimeout(toastTimer);
toastTimer = window.setTimeout(function () {
toastEl.classList.remove("show");
}, 2600);
}
track.addEventListener("click", function (e) {
var btn = e.target.closest ? e.target.closest(".share") : null;
if (!btn) return;
var couple = btn.getAttribute("data-couple") || "this couple";
var slug = couple.toLowerCase().replace(/[^a-z]+/g, "-").replace(/^-|-$/g, "");
var link = window.location.origin + "/stories/" + slug;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(link).then(
function () {
toast(couple + "’s story link copied");
},
function () {
toast("Story: " + couple);
}
);
} else {
toast("Story: " + couple);
}
});
/* ---------- Live rating summary ---------- */
(function summarize() {
var totalStars = 0;
slides.forEach(function (s) {
var label = s.querySelector(".stars").getAttribute("aria-label") || "";
var m = label.match(/(\d+)/);
totalStars += m ? parseInt(m[1], 10) : 5;
});
var avg = totalStars / count;
var avgEl = document.getElementById("avgRating");
var countEl = document.getElementById("totalCount");
if (avgEl) avgEl.textContent = avg.toFixed(1);
if (countEl) countEl.textContent = String(count);
})();
/* ---------- Boot ---------- */
render();
start();
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Wedding — Testimonials</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@400;500;600&family=Inter:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main class="wrap">
<section class="testimonials" aria-labelledby="tt-title">
<header class="head">
<p class="eyebrow"><span class="rule"></span>Love, in their words<span class="rule"></span></p>
<h1 id="tt-title">Kind words from our couples</h1>
<p class="sub">
Real celebrations, real stories — a few of the couples who trusted us with
the most beautiful day of their lives.
</p>
<dl class="stats" aria-label="Testimonial summary">
<div class="stat">
<dt>Average rating</dt>
<dd><span id="avgRating">5.0</span> <span class="stat-star" aria-hidden="true">★</span></dd>
</div>
<div class="stat">
<dt>Celebrations</dt>
<dd><span id="totalCount">6</span></dd>
</div>
<div class="stat">
<dt>Would recommend</dt>
<dd>100<span class="pct">%</span></dd>
</div>
</dl>
</header>
<div class="carousel" id="carousel">
<button class="nav prev" id="prevBtn" type="button" aria-label="Previous story">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 5l-7 7 7 7" /></svg>
</button>
<div class="viewport" aria-live="polite">
<ul class="track" id="track">
<!-- Slide 1 -->
<li class="slide" aria-roledescription="slide" aria-label="1 of 6">
<article class="card">
<figure class="portrait">
<img src="https://images.unsplash.com/photo-1519741497674-611481863552?auto=format&fit=crop&w=480&q=80" alt="Amara and Julian on their wedding day" loading="lazy" />
</figure>
<div class="body">
<span class="quote-mark" aria-hidden="true">“</span>
<div class="stars" role="img" aria-label="5 out of 5 stars">★★★★★</div>
<blockquote>
Every tiny detail we dreamed of was there — the candlelight, the peonies,
the way our families finally met. We did nothing but be present and in love.
</blockquote>
<figcaption class="who">
<span class="names">Amara & Julian</span>
<span class="meta"><span class="date">June 14, 2025</span> · <span class="venue">Villa Estelle, Lake Como</span></span>
</figcaption>
<button class="share" type="button" data-couple="Amara & Julian">Copy their story link</button>
</div>
</article>
</li>
<!-- Slide 2 -->
<li class="slide" aria-roledescription="slide" aria-label="2 of 6">
<article class="card">
<figure class="portrait">
<img src="https://images.unsplash.com/photo-1606216794074-735e91aa2c92?auto=format&fit=crop&w=480&q=80" alt="Priya and Noah under a floral arch" loading="lazy" />
</figure>
<div class="body">
<span class="quote-mark" aria-hidden="true">“</span>
<div class="stars" role="img" aria-label="5 out of 5 stars">★★★★★</div>
<blockquote>
They turned a rainy forecast into the most magical golden-hour ceremony.
Our guests still talk about the string quartet and the way the tent glowed.
</blockquote>
<figcaption class="who">
<span class="names">Priya & Noah</span>
<span class="meta"><span class="date">September 2, 2025</span> · <span class="venue">The Old Orchard Barn</span></span>
</figcaption>
<button class="share" type="button" data-couple="Priya & Noah">Copy their story link</button>
</div>
</article>
</li>
<!-- Slide 3 -->
<li class="slide" aria-roledescription="slide" aria-label="3 of 6">
<article class="card">
<figure class="portrait">
<img src="https://images.unsplash.com/photo-1583939003579-730e3918a45a?auto=format&fit=crop&w=480&q=80" alt="Sofia and Mateo laughing together" loading="lazy" />
</figure>
<div class="body">
<span class="quote-mark" aria-hidden="true">“</span>
<div class="stars" role="img" aria-label="5 out of 5 stars">★★★★★</div>
<blockquote>
From the first mood board to the last dance, we felt so held. They anticipated
everything before we even asked. Worth every single moment of trust.
</blockquote>
<figcaption class="who">
<span class="names">Sofia & Mateo</span>
<span class="meta"><span class="date">May 24, 2025</span> · <span class="venue">Casa Marbella, Seville</span></span>
</figcaption>
<button class="share" type="button" data-couple="Sofia & Mateo">Copy their story link</button>
</div>
</article>
</li>
<!-- Slide 4 -->
<li class="slide" aria-roledescription="slide" aria-label="4 of 6">
<article class="card">
<figure class="portrait">
<img src="https://images.unsplash.com/photo-1511285560929-80b456fea0bc?auto=format&fit=crop&w=480&q=80" alt="Hannah and Elias sharing a first look" loading="lazy" />
</figure>
<div class="body">
<span class="quote-mark" aria-hidden="true">“</span>
<div class="stars" role="img" aria-label="5 out of 5 stars">★★★★★</div>
<blockquote>
We planned a wedding from three time zones away and never once felt stressed.
Calm, warm, endlessly organized — our day unfolded like a quiet dream.
</blockquote>
<figcaption class="who">
<span class="names">Hannah & Elias</span>
<span class="meta"><span class="date">October 11, 2025</span> · <span class="venue">Rosewood Cliffs, Amalfi</span></span>
</figcaption>
<button class="share" type="button" data-couple="Hannah & Elias">Copy their story link</button>
</div>
</article>
</li>
<!-- Slide 5 -->
<li class="slide" aria-roledescription="slide" aria-label="5 of 6">
<article class="card">
<figure class="portrait">
<img src="https://images.unsplash.com/photo-1537633552985-df8429e8048b?auto=format&fit=crop&w=480&q=80" alt="Grace and Theo dancing at their reception" loading="lazy" />
</figure>
<div class="body">
<span class="quote-mark" aria-hidden="true">“</span>
<div class="stars" role="img" aria-label="4 out of 5 stars">★★★★<span class="star-empty">★</span></div>
<blockquote>
The reception was pure joy — a live band, a long banquet table, sparklers at
midnight. They made two big families feel like one for a whole beautiful night.
</blockquote>
<figcaption class="who">
<span class="names">Grace & Theo</span>
<span class="meta"><span class="date">July 19, 2025</span> · <span class="venue">Maple Hill Estate</span></span>
</figcaption>
<button class="share" type="button" data-couple="Grace & Theo">Copy their story link</button>
</div>
</article>
</li>
<!-- Slide 6 -->
<li class="slide" aria-roledescription="slide" aria-label="6 of 6">
<article class="card">
<figure class="portrait">
<img src="https://images.unsplash.com/photo-1522673607200-164d1b6ce486?auto=format&fit=crop&w=480&q=80" alt="Lena and Adam at their garden ceremony" loading="lazy" />
</figure>
<div class="body">
<span class="quote-mark" aria-hidden="true">“</span>
<div class="stars" role="img" aria-label="5 out of 5 stars">★★★★★</div>
<blockquote>
Intimate, elegant, and completely us. Twenty of our dearest people, a garden full
of wildflowers, and a team that treated our small day like the grandest affair.
</blockquote>
<figcaption class="who">
<span class="names">Lena & Adam</span>
<span class="meta"><span class="date">August 30, 2025</span> · <span class="venue">The Secret Garden, Kent</span></span>
</figcaption>
<button class="share" type="button" data-couple="Lena & Adam">Copy their story link</button>
</div>
</article>
</li>
</ul>
</div>
<button class="nav next" id="nextBtn" type="button" aria-label="Next story">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 5l7 7-7 7" /></svg>
</button>
</div>
<div class="dots" id="dots" role="tablist" aria-label="Choose a story"></div>
</section>
</main>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>A refined couple-testimonials block built for wedding planners, venues and photographers. Each slide is a quote card that pairs a soft-framed couple portrait with the wedding date, venue and a five-star rating, set in an airy blush-and-gold editorial style. Cormorant Garamond headings and thin gold rules give the section a romantic, magazine-like calm while keeping text at WCAG AA contrast.
The carousel is the centerpiece. A single story is shown at a time and glides horizontally with a smooth transition. Visitors can move between stories with the gold prev/next buttons, click a dot to jump directly, use the left/right arrow keys, or swipe on touch devices. Autoplay advances the stories on a gentle timer and politely pauses whenever the pointer is over the card or a control is focused, so nobody loses their place mid-read.
Above the track, a live summary tallies the average rating and total celebrations, and a small toast confirms interactions like copying a couple’s story link. Everything is vanilla HTML, CSS and JavaScript — no frameworks, no build step — and the layout collapses gracefully to a single comfortable column down to 360px.