Skip to content
txbrendan/ui

Testimonial Wall

beta

A 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. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the website component. It also adds every component it uses. Its own files go in your components folder, 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

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." },
  ]}
/>