Testimonial Wall
betaA headline over a masonry wall of quote cards, each with a name, role, and avatar, and an optional Show more.
Built from Avatar, Button, and Motion Timing.
Preview
Loved by team leads, and their teams too.
Teams of every size run their week here.
Monday mornings used to start with forty messages. Now everyone answers in one place, and I know who is coming before I book the room.
Priya RamanDesign lead at a studio Setup took an afternoon.
Tomás OkaforOffice manager at a clinic People stopped asking me what time the all-hands is. That alone was worth it.
Elin SatoTeam lead at a library We had three spreadsheets, a group chat, and a whiteboard in the office. Now we have one list that everyone trusts, and the whiteboard is for ideas again.
Marcus BellHead of operations at a bakery I can see who is out before the shift starts, so I plan the queue around them.
Ana LindqvistSupport lead at an agency Clear enough that our newest hire set up the quarter's events on her own.
Dev KapoorOps assistant at a workshop
every quote, no headline
Monday mornings used to start with forty messages. Now everyone answers in one place, and I know who is coming before I book the room.
Priya RamanDesign lead at a studio Setup took an afternoon.
Tomás OkaforOffice manager at a clinic People stopped asking me what time the all-hands is. That alone was worth it.
Elin SatoTeam lead at a library We had three spreadsheets, a group chat, and a whiteboard in the office. Now we have one list that everyone trusts, and the whiteboard is for ideas again.
Marcus BellHead of operations at a bakery I can see who is out before the shift starts, so I plan the queue around them.
Ana LindqvistSupport lead at an agency
narrow container: one column
What teams say
Monday mornings used to start with forty messages. Now everyone answers in one place, and I know who is coming before I book the room.
Priya RamanDesign lead at a studio Setup took an afternoon.
Tomás OkaforOffice manager at a clinic People stopped asking me what time the all-hands is. That alone was worth it.
Elin SatoTeam lead at a library
Install
1. Add the registry to
components.json, once per app. Without it, the shadcn CLI looks up@txbrendanin a public list that txbrendan/ui does not control.npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.
npx shadcn@latest add @txbrendan/theme3. Add the website component. It also adds every component it uses. Its own files go in your
componentsfolder, and they are yours to edit.npx shadcn@latest add @txbrendan/testimonial-wall
Use it for
- The social proof section of a landing or pricing page, between the features and the FAQ.
- Six to twelve quotes of mixed length. Short and long ones side by side make the wall.
- Give each person a `role` that says who they are and where, like "Design lead at a studio".
- Set `limit` to six or so when you have many quotes. The rest wait behind Show more.
- Give it a `title`, or an aria-label when the section has no headline.
- The headline is set in the theme's `font-headline`. Load Geist Serif as `--font-geist-serif`, like `geist/font/serif/variable` in Next.js, or it falls back to the system serif.
Avoid it for
- Quotes you made up or edited past what the person said. Ask each one first.
- One or two quotes. Put them in the hero, or in a plain row.
- Star ratings or reviews from a store. Link to the store instead.
- A wall of logos. Use Logo Marquee.
Props
- testimonials
The quotes, in the order to read them. Each name must be unique, since it is the key. avatar is a photo URL. Without one, the initials show.
{ quote: ReactNode; name: string; role?: ReactNode; avatar?: string }[]
- title
The headline's first words, in full ink. It grows with the wall's width, from 28 to 36px.
ReactNode
- muted
The words that follow the title in a quieter gray, on the same line.
ReactNode
- description
A line or two under the headline.
ReactNode
- as
The headline's level.
"h2" | "h3" · default "h2"
- limit
Shows this many quotes, then a Show more button for the rest. The bottom of the wall fades while some are hidden. The rest open as a second wall under the first, so no card you already see moves.
number · default all of them
- showMoreLabel
The button's words while quotes are hidden.
string · default "Show more"
- showLessLabel
The button's words once every quote shows.
string · default "Show less"
- className
Extra classes on the section, which holds the container query. One column when narrow, two from 36rem, three from 64rem.
string
| Prop | Type | Default | What it does |
|---|---|---|---|
| testimonials | { quote: ReactNode; name: string; role?: ReactNode; avatar?: string }[] | - | The quotes, in the order to read them. Each name must be unique, since it is the key. avatar is a photo URL. Without one, the initials show. |
| title | ReactNode | - | The headline's first words, in full ink. It grows with the wall's width, from 28 to 36px. |
| muted | ReactNode | - | The words that follow the title in a quieter gray, on the same line. |
| description | ReactNode | - | A line or two under the headline. |
| as | "h2" | "h3" | "h2" | The headline's level. |
| limit | number | all of them | Shows this many quotes, then a Show more button for the rest. The bottom of the wall fades while some are hidden. The rest open as a second wall under the first, so no card you already see moves. |
| showMoreLabel | string | "Show more" | The button's words while quotes are hidden. |
| showLessLabel | string | "Show less" | The button's words once every quote shows. |
| className | string | - | Extra classes on the section, which holds the container query. One column when narrow, two from 36rem, three from 64rem. |
Accessibility
- The wall is a list. Each card is a figure: the quote is a blockquote, and the name and role are its caption.
- The avatar is hidden from screen readers, since the name is written beside it.
- Screen readers read the quotes in the order you give them, which runs down the first column, then the next.
- Hidden quotes are not on the page, so screen readers never read a quote people cannot see. Show more adds the rest as a second list under the first.
- Show more is a button with aria-expanded, and aria-controls points at the list. Focus stays on it when it opens or closes.
- In forced colors each card gets a border, and the fade goes away.
Examples
Under a headline, with Show more
<TestimonialWall
title="Loved by team leads,"
muted="and their teams too."
limit={6}
testimonials={[
{ name: "Priya Raman", role: "Design lead at a studio", quote: "Now everyone answers in one place." },
{ name: "Tomás Okafor", role: "Office manager at a clinic", quote: "Setup took an afternoon." },
{ name: "Elin Sato", role: "Team lead at a library", quote: "People stopped asking me what time the all-hands is." },
]}
/>With photos and no headline
<TestimonialWall
aria-label="What teams say"
testimonials={[
{ name: "Priya Raman", role: "Design lead", avatar: "/people/priya.jpg", quote: "Now everyone answers in one place." },
{ name: "Tomás Okafor", role: "Office manager", avatar: "/people/tomas.jpg", quote: "Setup took an afternoon." },
]}
/>