Testimonial Wall
block / marketing
Social proof in three arrangements off one typed list — a masonry column wall, two opposed marquee rows, and a featured lead quote.
- type
- block
- files
- 2
- registry deps
- card, skeleton, @automindz/content-card, @automindz/motion-primitives, @automindz/render-link
- npm deps
- lucide-react
installs
- lib/automindz/testimonials.ts
- components/automindz/testimonial-wall.tsx
- A row's cards are one height; the byline sits at the floor, not mid-card.
- columns stays masonry: natural heights, but column reading order.
- Every card answers hover; only the linked ones lift.
- Stars are aria-hidden; one sr-only line carries the rating.
- The bottom fade is off by default — nothing is cut, so nothing is dimmed.
Edit lib/automindz/testimonials.ts, not the component — the nine defaults are invented placeholders and carry no avatar on purpose, because a default headshot path renders a broken image in every install (the wall typesets initials instead). layout changes everything: columns is CSS multi-column, so cards keep their natural height and **reading order runs down each column** — correct for unordered social proof, wrong for anything ordered; marquee reuses Marquee from @automindz/motion-primitives as two opposed rows and **needs import "@/styles/automindz-motion.css" once in your root layout**, without which the rows are static and scrollable rather than frozen; featured leads with the quote flagged featured one step up the type scale over a primary rule and rows the rest. In marquee and featured every card in a row is the same height and the byline sits at the card's floor — the wall sets no height on a card, so never add h-full to one. Quotes are never clamped: a truncated testimonial is worse than an uneven one. Every card answers hover and keyboard focus; only the ones with an href add a lift and underline the name. The bottom fade is **off by default**: this wall renders every quote it is given, so a fade promises content below a cut that is not there and only washes out the last card in the tallest column. Turn it on when you have clipped the wall yourself. rating prints glyphs plus an sr-only "Rated n out of 5", never colour alone. An empty list renders nothing at all. Pass renderLink for next/link. @automindz/content-card is here for CardGrid only — styles/automindz-card.css is not needed.
Variations
one state per section
- 01Columns, nine quotes
- 02Two columns, mixed set
- 03Marquee, two rows
- 04Featured, one lead plus a row
- 05Loading, same layout, same count
- 06No testimonials
Columns, nine quotes
CSS multi-column, not a grid, and the one layout where uneven heights are the answer rather than the bug: every card is its natural height, so a two-line quote next to a six-line one leaves no hole. The price is that reading order runs down each column rather than across — fine for social proof, wrong for anything ordered. Every quote here is fully rendered: the bottom fade is off by default, because a fade over nothing just washes out the last card in the tallest column.
Two columns, mixed set
Point at any card, linked or not: every one answers with a ground and a ring, and only the four that carry an href add the lift and the underline under the name — a static card that rises under the cursor is promising a click it will not honour. Nobody in the default list has a headshot, so every portrait is typeset initials, and the Meridian quote carries no rating rather than five empty stars.
Marquee, two rows
Every card in a row is the same height — the cards set no height of their own, so the row stretches them, and the byline is pinned to the floor by mt-auto so a short quote gets air rather than a hole. Two rows in opposite directions at slightly different speeds so they never lock into phase. Hover a row to pause it; tab into one and it pauses whether you asked for that or not.
Featured, one lead plus a row
The quote flagged featured in the config leads one step up the type scale over a 3px rule in the consumer's own primary token — spent on a mark, never on a sentence. The rest fall into an equal-height three-up grid underneath; pass four testimonials for the canonical shape, more simply wrap.
Loading, same layout, same count
Skeletons in the arrangement the real quotes will land in, one per entry — six when the list is still empty, which is the usual case. Each line is h-[1lh] of the type it stands in for, so the rating row, three lines of quote and the byline reserve exactly the height the copy will take and the page does not settle when the data arrives. One sr-only "Loading testimonials" for the wall, not six.
No testimonials
Two walls are rendered here and only the first is on the page: an empty list returns null rather than an EmptyState. Nobody arrives at a marketing page looking for the testimonials that are missing, and a dashed box reading "No testimonials yet" is an admission printed at the width of the page.
