16 — Testimonials
💬 Chapter 16 — Testimonials
The testimonials section reuses the auto-fit grid pattern from Chapter 14's services grid, then goes further — a decorative ::before quote mark, a colour-coded avatar per card, and the margin-top: auto trick that keeps every card's author row pinned to the same baseline, no matter how long each quote is.
HTML structure
Three testimonial cards sit inside a responsive grid. The section also has a centred header — label, heading, and subheading — before the grid.
<section class="testimonials">
<div class="container">
<!-- Centred header -->
<div class="testi-header reveal">
<p class="section-label">Kind words</p>
<h2 class="section-title">Clients who became partners</h2>
<p class="section-sub">Don't take our word for it.</p>
</div>
<!-- Card grid -->
<div class="testi-grid">
<div class="t-card reveal">
<div class="t-stars">★★★★★</div>
<blockquote>Lumen took our scrappy idea...</blockquote>
<div class="t-author">
<div class="t-avatar">SM</div>
<div class="t-author-info">
<strong>Sara M.</strong>
<span>Founder, Bloom</span>
</div>
</div>
</div>
<!-- × 2 more .t-card -->
</div>
</div>
</section>
Each .t-card has four children: stars, blockquote, and author (avatar + info).
CSS — section and header
/* ── Step 10 · Testimonials ──────────────────────── */
.testimonials {
padding: clamp(4rem, 10vw, 7rem) 0;
background: var(--clr-bg-alt); /* soft grey — contrast from white About */
}
.testi-header {
text-align: center;
max-width: 600px;
margin: 0 auto clamp(2.5rem, 6vw, 4rem);
}
max-width + margin: 0 auto keeps the header from stretching too wide on large screens — the same centred-container pattern from Chapter 11.
CSS — the auto-fit grid
This is the same auto-fit + minmax pattern used for the Services grid in Chapter 14 — it creates a responsive layout with no media queries at all.
.testi-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
}
With 3 cards and a 280 px minimum: three columns on wide screens, two on tablet, one on mobile — all automatic.
How auto-fit + minmax works — visualised
Grid behaviour at different container widths
Wide (≥ 900 px)
Tablet (≥ 580 px)
Mobile (< 580 px)
auto-fit
/* Collapses empty tracks —
cards stretch to fill
the full row width */
auto-fill
/* Keeps empty tracks —
cards don't stretch,
leaving gaps on the right */
auto-fit when you want cards to grow and fill the row. Use auto-fill when you want items to stay at their minimum size and leave trailing space — useful for horizontally scrolling carousels.
CSS — base card styles
.t-card {
background: var(--clr-bg); /* white card on grey section */
border-radius: var(--radius);
padding: clamp(1.5rem, 3vw, 2rem);
border: 1px solid var(--clr-border);
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
display: flex;
flex-direction: column;
gap: 1rem;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.t-card:hover {
transform: translateY(-5px);
box-shadow: 0 14px 36px rgba(0,0,0,0.11);
}
display: flex; flex-direction: column stacks the four child elements (stars → quote → author) vertically with consistent spacing via gap.
Live demo — grid + card base
Cards have placeholder blocks where the quote, stars, and avatar will go before we style the fine details below. Hover a card to see the lift. Toggle the card outline to inspect the flex column layout.
Kind words
Clients who became partners
Don't take our word for it.
Card anatomy
Each .t-card has four styled children. The flex-direction: column + gap from the base card spaces them automatically — we just need to style each piece.
" generated by ::before, positioned absolutely at the top-left of the blockquote.
margin-top: auto pins it to the bottom of the card so all cards align their author rows — even when quote lengths differ.
:nth-child.
CSS — stars
.t-stars {
color: #fbbf24; /* amber — familiar rating colour */
font-size: 1rem;
letter-spacing: 2px; /* breathe the stars apart */
line-height: 1;
}
CSS — blockquote + decorative opening quote
The large " is injected by ::before and positioned absolutely at the top-left of the blockquote. This is the same pattern from Chapter 15's badge — a position: relative parent and a position: absolute child.
.t-card blockquote {
margin: 0;
padding: 0.75rem 0 0; /* top gap — room for the quote mark */
position: relative; /* containing block for ::before */
font-size: 0.95rem;
color: var(--clr-muted);
line-height: 1.7;
font-style: normal; /* browsers italicise blockquote by default */
}
.t-card blockquote::before {
content: '\201C'; /* Unicode left double quotation mark " */
position: absolute;
top: -0.6rem;
left: -0.2rem;
font-size: 4rem;
line-height: 1;
font-family: Georgia, serif; /* serif gives a classic quote character */
color: var(--clr-accent);
opacity: 0.15; /* subtle watermark — not in the way */
pointer-events: none;
}
pointer-events: none on the pseudo-element means it never intercepts mouse events — the text behind it stays selectable.
\201C: In CSS content, Unicode characters are written as a backslash followed by the hex code point — no u or braces. \201C = " (left double quote), \201D = " (right). Alternatively you can write the character directly: content: '"'.
CSS — author row + avatar
.t-author {
display: flex;
align-items: center;
gap: 0.75rem;
margin-top: auto; /* push to bottom of flex column — equal-height alignment */
padding-top: 0.75rem;
border-top: 1px solid var(--clr-border);
}
.t-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--clr-accent);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: 0.8rem;
flex-shrink: 0;
letter-spacing: 0.03em;
}
/* Different accent colour per card */
.testi-grid .t-card:nth-child(2) .t-avatar { background: #7c5cbf; }
.testi-grid .t-card:nth-child(3) .t-avatar { background: #0d9488; }
.t-author-info {
display: flex;
flex-direction: column;
gap: 2px;
}
.t-author-info strong { font-size: 0.88rem; color: var(--clr-text); }
.t-author-info span { font-size: 0.78rem; color: var(--clr-muted); }
margin-top: auto on the last child absorbs all remaining space, pinning that element to the bottom. When your three cards have quotes of different lengths, the author row stays level across all of them — no JavaScript height-matching needed.
Live demo — complete testimonial cards
Hover cards for the lift. Toggle the annotations to highlight the ::before quote mark and avatar circles.
Lumen took our scrappy idea and turned it into a brand we're genuinely proud to show the world.
Working with Lumen felt less like hiring an agency and more like gaining a creative partner who genuinely cared.
On time, on budget, and genuinely beautiful. I've worked with a lot of studios — Lumen is in a different league.
Step 10 — complete
- Grid + card base —
auto-fit, hover lift, flex column ✓ - Stars — amber, letter-spaced ✓
- Blockquote —
position: relative+::beforewatermark quote mark ✓ - Author row —
margin-top: autoequal-height pin, divider border ✓ - Avatar — initials circle, per-card colour via
:nth-child✓
::before/::after circles — followed by the four-column footer.