Nº 155 of 520
Testimonials quote carousel
A horizontal Fieldnote quote carousel: 48px Newsreader lines, author chip, prev/next, four dots, and a 420ms slide that wraps.
Preview
Use it
Free to use · credit appreciated1 Once, in your project
npx skills add SusanAcharya/Design-Lounge 2 Then ask
Using the Design Lounge skill, build "Testimonials quote carousel" (piece testimonials-quote-carousel) in my stack. Match its brief; restyle it onto my product's theme.
No skill? Paste the brief instead. It carries the numbers, tokens and behaviour.
<!-- Design Lounge Nº 155 · "Testimonials quote carousel" · www.designlounge.live -->
# Testimonials quote carousel
> **Build brief for a coding agent.** Rebuild this piece in the reader's stack. If they haven't said which stack, ask once, then default to semantic HTML + CSS + a little vanilla JS. Match the numbers below; don't "improve" them.
## What it is
A mid-page testimonials section for **Fieldnote**, a B2B field-research tool. One quote fills the viewport at a time: a 72px decorative opening mark, a 48px Newsreader sentence (one italic clay word), then a 40px initials disc and a name/role line. Controls sit on a single row: 44px prev/next squares and four 8px dots. It is a carousel, not a wall — the point is the scale of a single sentence and the quiet wrap from 04 back to 01. The first frame already shows quote 01.
## Reference behaviour
1. Initial state: paper page, 56px nav (Fieldnote mark + Product / Methods / Pricing + “Request a seat”), then the section. Index reads **01 / 04**. Kicker on the right: “WHAT TEAMS WRITE BACK”. Quote 01 is on: “We stopped losing the week *after* the site visit.” Author Priya Sen, Research lead · Northline Labs, pine disc “PS”. Dot 1 has `aria-current="true"`.
2. Click Next (or press ArrowRight): quote 01 fades and slides 28px left over 420ms; quote 02 enters from 28px right. Index becomes **02 / 04**. Live region announces “Quote 2 of 4, Tomas Brekke”.
3. Click Prev (or press ArrowLeft) from 01: wraps to 04 (Jonah Ndiaye) with a reverse 28px slide. Index **04 / 04**.
4. Click a dot: jump to that quote. Direction is `back` if the target index is lower, `fwd` if higher.
5. Clicks during the 420ms lock are ignored so slides cannot stack.
6. Hover a nav square: fill `--ink`, icon `--bg`, border `--ink`. Hover a closed dot: `--ink-2`. Active dot is `--pine` at scale 1.15.
7. Hover “Request a seat”: invert to `--ink` fill, `--bg` text.
8. With `prefers-reduced-motion: reduce`, the slide duration is 1ms and transforms are forced to none; state still updates.
## Structure
```
1280 × 800
┌──────────────────────────────────────────────────────────────────────┐
│ ● Fieldnote Product Methods Pricing Request a seat │ 56
├──────────────────────────────────────────────────────────────────────┤
│ 01 / 04 WHAT TEAMS WRITE BACK │ 36 pad
│ │
│ “ │ 72 mark
│ We stopped losing the week after │
│ the site visit. │ 48 / 1.12
│ │
│ (PS) Priya Sen │
│ Research lead · Northline Labs │
│ │
│ [←] [→] ● ○ ○ ○ │ 44 btns
└──────────────────────────────────────────────────────────────────────┘
pad 72
… 147 more lines. Copy or download for the full brief. Loading the source…Brief
187 linesBuild brief for a coding agent. Rebuild this piece in the reader’s stack. If they haven’t said which stack, ask once, then default to semantic HTML + CSS + a little vanilla JS. Match the numbers below; don’t “improve” them.
What it is
A mid-page testimonials section for Fieldnote, a B2B field-research tool. One quote fills the viewport at a time: a 72px decorative opening mark, a 48px Newsreader sentence (one italic clay word), then a 40px initials disc and a name/role line. Controls sit on a single row: 44px prev/next squares and four 8px dots. It is a carousel, not a wall — the point is the scale of a single sentence and the quiet wrap from 04 back to 01. The first frame already shows quote 01.
Reference behaviour
- Initial state: paper page, 56px nav (Fieldnote mark + Product / Methods / Pricing + “Request a seat”), then the section. Index reads 01 / 04. Kicker on the right: “WHAT TEAMS WRITE BACK”. Quote 01 is on: “We stopped losing the week after the site visit.” Author Priya Sen, Research lead · Northline Labs, pine disc “PS”. Dot 1 has
aria-current="true". - Click Next (or press ArrowRight): quote 01 fades and slides 28px left over 420ms; quote 02 enters from 28px right. Index becomes 02 / 04. Live region announces “Quote 2 of 4, Tomas Brekke”.
- Click Prev (or press ArrowLeft) from 01: wraps to 04 (Jonah Ndiaye) with a reverse 28px slide. Index 04 / 04.
- Click a dot: jump to that quote. Direction is
backif the target index is lower,fwdif higher. - Clicks during the 420ms lock are ignored so slides cannot stack.
- Hover a nav square: fill
--ink, icon--bg, border--ink. Hover a closed dot:--ink-2. Active dot is--pineat scale 1.15. - Hover “Request a seat”: invert to
--inkfill,--bgtext. - With
prefers-reduced-motion: reduce, the slide duration is 1ms and transforms are forced to none; state still updates.
Structure
1280 × 800
┌──────────────────────────────────────────────────────────────────────┐
│ ● Fieldnote Product Methods Pricing Request a seat │ 56
├──────────────────────────────────────────────────────────────────────┤
│ 01 / 04 WHAT TEAMS WRITE BACK │ 36 pad
│ │
│ “ │ 72 mark
│ We stopped losing the week after │
│ the site visit. │ 48 / 1.12
│ │
│ (PS) Priya Sen │
│ Research lead · Northline Labs │
│ │
│ [←] [→] ● ○ ○ ○ │ 44 btns
└──────────────────────────────────────────────────────────────────────┘
pad 72
<nav>56px:.brand(18px ring mark + word),.linksof three<a>,.ctabutton.<section aria-roledescription="carousel" aria-label="Customer quotes">is a 3-row grid:.head,.stage,.foot..head:#ncurrent index + “ / 04”,.kicker..stageholds four<article class="slide">. The on-slide isposition:relativeso the stage takes its height; others areposition:absolute; inset:0.- Each slide: decorative
.qmark,<blockquote><p>,.who(.av+ name/role). .foot:#prev,#next,.dotsrole="tablist"with fourrole="tab"buttons.#liveis a clipped polite live region.
Quotes, in order:
| # | Sentence (italic word) | Name | Role · company | Disc |
|---|---|---|---|---|
| 01 | We stopped losing the week after the site visit. | Priya Sen | Research lead · Northline Labs | #2C5A45 PS |
| 02 | Four cities, one thread, tagged before the debrief. | Tomas Brekke | Operations · Harbor & Co | #3D4A62 TB |
| 03 | Friday used to be transcripts. Now I leave the field done. | Elena Voss | Partner · Kite Field | #B5522A EV |
| 04 | The codebook stopped living in a private spreadsheet. | Jonah Ndiaye | Director of insight · Vale Civic | #5A4638 JN |
Tokens
:root {
--bg: #efe6d4; /* page */
--paper: #f6efe2; /* button faces */
--ink: #1a241e; /* primary text */
--ink-2: #5a5648; /* secondary */
--ink-3: #8a8374; /* index / meta */
--line: #d4cbb8; /* nav rule */
--line-2: #c4b9a4; /* button border, idle dots */
--pine: #2c5a45; /* kicker, active dot, focus, mark fill */
--clay: #b5522a; /* italic word in the quote */
--mark: #d8cbb4; /* giant opening quote */
--serif: "Newsreader", Georgia, serif;
--sans: "Work Sans", system-ui, sans-serif;
--nav-h: 56px;
--pad: 72px;
--quote: 48px;
--qmark: 72px;
--btn: 44px;
--t-fast: 160ms;
--t-slide: 420ms;
--ease: cubic-bezier(.2, .7, .2, 1);
--ease-out: cubic-bezier(.16, 1, .3, 1);
}
Typography
| Role | Family | Size | Weight | Line-height | Tracking | Case |
|---|---|---|---|---|---|---|
| Body / nav links | Work Sans | 13–14px | 400 | 1.45 | 0 | sentence |
| Brand | Work Sans | 15px | 600 | 1 | −0.02em | sentence |
| CTA | Work Sans | 12px | 500 | 1 | +0.04em | UPPERCASE |
| Index / kicker | Work Sans | 12px | 500 | 1 | +0.14–0.16em | UPPERCASE |
| Opening mark | Newsreader | 72px | 600 | 0.7 | −0.06em | — |
| Quote | Newsreader | 48px | 400 | 1.12 | −0.025em | sentence |
| Quote emphasis | Newsreader | 48px | 600 italic | 1.12 | −0.025em | sentence |
| Author | Work Sans | 15px | 500 | 1.2 | 0 | sentence |
| Role line | Work Sans | 13px | 400 | 1.3 | 0 | sentence |
| Avatar initials | Work Sans | 13px | 500 | 1 | +0.02em | UPPERCASE |
Quote measure is max-width: 22ch on the 48px <p> (not the blockquote — ch on a 14px parent is ~144px and wraps every word). The line breaks at two or three lines, never a full-bleed ribbon.
Motion
| Element | Trigger | Property | From → To | Duration | Easing | Notes |
|---|---|---|---|---|---|---|
outgoing .slide | next/prev/dot | opacity, translateX | 1, 0 → 0, ±28px | 420ms | --ease | +28px if data-dir="fwd", −28px if back |
incoming .slide | same | opacity, translateX | 0, ∓28px → 1, 0 | 420ms | --ease | opposite start |
| lock | after go() | — | — | 430ms | — | ignore further go() until the clock ends |
.navbtn:active | click | translateY | 0 → 1px | 0 | — | instant press |
.dot[aria-current] | selection | background, scale | line-2, 1 → pine, 1.15 | 0 | — | instant |
Reduced motion: .slide { transition-duration: 1ms; transform: none !important }. Lock timeout becomes 1ms. No autoplay.
States
- Current slide:
data-on="1",opacity:1,position:relative. Othersopacity:0,pointer-events:none,position:absolute. - Nav button hover: background and border
--ink, icon--bg. - Nav button focus-visible: 2px
--pineoutline, 3px offset. Same ring on brand, links, CTA, dots. - Nav button active:
translateY(1px). - Dot current:
--pine, scale 1.15. Idle--line-2. Hover idle--ink-2. - CTA hover: inverted fill.
- CTA default: 32px tall, 1px
--inkborder, transparent fill, 12px uppercase.
Accessibility
- Section:
aria-roledescription="carousel"andaria-label="Customer quotes". - Prev/next are real
<button>s witharia-label="Previous quote"/"Next quote". - Dots:
role="tablist"; each dotrole="tab"andaria-label="Quote N"; the active one hasaria-current="true". - Each dot has a 10px invisible
::afterhit pad so the 8px face is still a ≥28px target; the 44px nav buttons already clear 40px. #live(aria-live="polite") announces “Quote N of 4, {Name}” after every change.- Decorative quote marks are
aria-hidden="true". - Keyboard: Tab through nav → prev → next → dots. ArrowLeft / ArrowRight change slides from anywhere on the page (
preventDefaultso the page does not scroll). - Contrast:
--inkon--bgis ~13:1;--ink-2on--bgis ~6.2:1;--pineon--bgis ~5.8:1; clay italic on paper is ~5.1:1.
Responsive rules
- ≥ 1280: as specified,
--pad: 72px, quote 48px, mark 72px. - 1024–1279:
--pad: 40px, quote 36px, mark 56px. Controls stay in one row. - 768–1023: quote 28px, mark 48px. Nav links hide; brand and CTA remain.
- < 640:
--pad: 24px. Quote stays 28px / 22ch. Stack.whounder the quote (already does). Keep buttons 44px. Dots remain 8px with the 10px hit pad.
Acceptance checklist
- First frame shows quote 01 at 48px Newsreader on
#EFE6D4, index 01 / 04, pine kicker on the right. Two or three lines, not one word per line. - Exactly four quotes; Next from 04 wraps to 01; Prev from 01 wraps to 04.
- Slide is 420ms, 28px horizontal,
cubic-bezier(.2,.7,.2,1); a second click inside that window does nothing. - One italic clay word per quote (
--clay#B5522A, weight 600). - Prev/next are 44×44px squares; active dot is pine at scale 1.15.
- ArrowLeft / ArrowRight change slides and do not scroll the page.
- Live region announces “Quote N of 4, {Name}”.
- Focus rings (2px pine, 3px offset) appear on brand, links, CTA, both buttons, and every dot.
-
prefers-reduced-motion: reduceswaps instantly; wrap and announcement still work. - No images, no emoji, no dummy copy, no amber-on-black palette.
- Demo starts with the piece header comment and fills 1280×800 without a vertical scrollbar.
Implementation notes
Only the current slide is in flow. Absolute-position the rest so the stage height equals the visible quote:
.slide { position: absolute; inset: 0; opacity: 0; pointer-events: none; }
.slide[data-on="1"] { position: relative; opacity: 1; pointer-events: auto; }
Direction on the outgoing card, then the incoming card. Set data-dir on both, paint the incoming off-screen in one frame, then flip data-on on the next frame so the 28px travel runs:
from.removeAttribute('data-on');
from.dataset.dir = dir;
next.dataset.dir = dir === 'back' ? 'fwd' : 'back';
requestAnimationFrame(() => { next.dataset.dir = dir; next.dataset.on = '1'; });
Lock for 430ms (1ms under reduced motion) so overlapping transitions cannot desync the index and the live region.
Common mistakes: using a horizontal scroller instead of one-quote-at-a-time; fading without the 28px travel; putting the giant quote mark in the accessibility tree; autoplaying (this piece does not); measuring scrollWidth when CSS already knows the active slide’s height.
Details
- Palette
- Type
- Newsreader · Work Sans
- Motion
- Subtle motion
- Build
- One session
- Tags
On the shelves