Testimonials

Testimonial layouts for real customer voices.

A few ways to let customer words do the talking. Replace the quotes, names, and avatars with the real thing.

One centered testimonial with an avatar and role.

“We dropped in a hero and a navbar and shipped the marketing page the same afternoon. It already looked like us.”

Maya Chen

Head of Design, Northline

tsx
"use client";import { cn } from "@/lib/utils";type Testimonials01Props = {  className?: string;};export function Testimonials01({ className }: Testimonials01Props) {  return (    <section className={cn("@container w-full bg-background", className)}>      <div className="mx-auto flex w-full max-w-2xl flex-col items-center px-4 py-12 text-center @[32rem]:px-6 @[32rem]:py-16">        <blockquote className="text-balance text-xl font-medium tracking-wide text-foreground @[32rem]:text-2xl">          “We dropped in a hero and a navbar and shipped the marketing page the          same afternoon. It already looked like us.”        </blockquote>        <div className="mt-8 flex flex-col items-center gap-3">          <img            src="/assets/brand/demos/avatars/avatar-1.png"            alt=""            className="size-10 rounded-full object-cover"          />          <div>            <p className="text-sm font-medium tracking-wide text-foreground">              Maya Chen            </p>            <p className="mt-0.5 text-xs tracking-wide text-muted-foreground">              Head of Design, Northline            </p>          </div>        </div>      </div>    </section>  );}

Installation

npx @arctis-sh/@arctis-sh/ui@latest add testimonials-01

Usage

import { Testimonials01 } from "@/components/blocks/testimonials/testimonials-01"
<Testimonials01 />

Three up

Three concise quotes in a responsive grid.

  • Token-ready blocks — we stopped rebuilding the same sections every launch.

    Jordan Hale

    Founder, Relay

  • CLI, drop-in, ship. Boring install story in the best way.

    Priya Nair

    Eng Manager, Cove

  • Light and dark just worked. Time went to product, not palette fights.

    Chris Ortega

    Product, Stackwell

tsx
"use client";import { cn } from "@/lib/utils";type Testimonials02Props = {  className?: string;};const TESTIMONIALS = [  {    quote: "Token-ready blocks — we stopped rebuilding the same sections every launch.",    name: "Jordan Hale",    role: "Founder, Relay",    avatar: "/assets/brand/demos/avatars/avatar-2.png",  },  {    quote: "CLI, drop-in, ship. Boring install story in the best way.",    name: "Priya Nair",    role: "Eng Manager, Cove",    avatar: "/assets/brand/demos/avatars/avatar-3.png",  },  {    quote: "Light and dark just worked. Time went to product, not palette fights.",    name: "Chris Ortega",    role: "Product, Stackwell",    avatar: "/assets/brand/demos/avatars/avatar-4.png",  },] as const;export function Testimonials02({ className }: Testimonials02Props) {  return (    <section className={cn("@container w-full bg-background", className)}>      <div className="mx-auto w-full max-w-3xl px-4 py-8 @[32rem]:px-6 @[32rem]:py-12">        <ul className="grid grid-cols-1 gap-3 @[40rem]:grid-cols-3">          {TESTIMONIALS.map((item) => (            <li              key={item.name}              className="flex flex-col rounded-xl bg-muted p-4"            >              <blockquote className="flex-1 text-xs tracking-wide text-foreground">{item.quote}              </blockquote>              <div className="mt-4 flex items-center gap-2.5">                <img                  src={item.avatar}                  alt=""                  className="size-7 shrink-0 rounded-full object-cover"                />                <div className="min-w-0">                  <p className="truncate text-xs font-medium tracking-wide text-foreground">                    {item.name}                  </p>                  <p className="truncate text-[11px] tracking-wide text-muted-foreground">                    {item.role}                  </p>                </div>              </div>            </li>          ))}        </ul>      </div>    </section>  );}

Installation

npx @arctis-sh/@arctis-sh/ui@latest add testimonials-02

Usage

import { Testimonials02 } from "@/components/blocks/testimonials/testimonials-02"
<Testimonials02 />

Marquee

An infinite ticker of compact review cards.

What teams say after they ship

tsx
"use client";import { cn } from "@/lib/utils";type Testimonials03Props = {  className?: string;};const TESTIMONIALS = [  {    quote: "Shipped our launch site in a day.",    name: "Maya Chen",    role: "Northline",    avatar: "/assets/brand/demos/avatars/avatar-1.png",  },  {    quote: "Finally blocks that match our tokens.",    name: "Jordan Hale",    role: "Relay",    avatar: "/assets/brand/demos/avatars/avatar-2.png",  },  {    quote: "CLI install, drop in, done.",    name: "Priya Nair",    role: "Cove",    avatar: "/assets/brand/demos/avatars/avatar-3.png",  },  {    quote: "Dark mode without the cleanup pass.",    name: "Chris Ortega",    role: "Stackwell",    avatar: "/assets/brand/demos/avatars/avatar-4.png",  },  {    quote: "Looks custom without the custom bill.",    name: "Sam Okonkwo",    role: "Fieldkit",    avatar: "/assets/brand/demos/avatars/avatar-5.png",  },] as const;function QuoteCard({  quote,  name,  role,  avatar,}: (typeof TESTIMONIALS)[number]) {  return (    <figure className="flex w-[18rem] shrink-0 flex-col justify-between rounded-xl border border-border bg-background p-5">      <blockquote className="text-sm tracking-wide text-foreground">{quote}      </blockquote>      <figcaption className="mt-5 flex items-center gap-3">        <img          src={avatar}          alt=""          className="size-8 shrink-0 rounded-full object-cover"        />        <div className="min-w-0">          <p className="truncate text-sm font-medium tracking-wide text-foreground">            {name}          </p>          <p className="truncate text-xs tracking-wide text-muted-foreground">            {role}          </p>        </div>      </figcaption>    </figure>  );}export function Testimonials03({ className }: Testimonials03Props) {  const track = [...TESTIMONIALS, ...TESTIMONIALS, ...TESTIMONIALS];  return (    <section className={cn("w-full overflow-hidden bg-background", className)}>      <style>{`        @keyframes testimonials-03-scroll {          from { transform: translateX(0); }          to { transform: translateX(-33.333%); }        }      `}</style>      <div className="flex w-full flex-col gap-5 py-10">        <p className="px-6 text-left text-xs tracking-wide text-muted-foreground">          What teams say after they ship        </p>        <div className="w-full overflow-hidden" aria-hidden="true">          <div            className="flex w-max gap-3 pr-3"            style={{ animation: "testimonials-03-scroll 40s linear infinite" }}          >            {track.map((item, i) => (              <QuoteCard key={`${item.name}-${i}`} {...item} />            ))}          </div>        </div>      </div>    </section>  );}

Installation

npx @arctis-sh/@arctis-sh/ui@latest add testimonials-03

Usage

import { Testimonials03 } from "@/components/blocks/testimonials/testimonials-03"
<Testimonials03 />