# Arc Scroll: Testimonials

> Wide quote cards with tilt turned off so the text stays level, plus page dots.

A shadcn/ui block from the [@keyframeui](https://keyframeui.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/keyframeui/arc-scroll-testimonials-demo).

- Page, with a live preview: https://shadcn.exchange/r/keyframeui/arc-scroll-testimonials-demo
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/keyframeui/arc-scroll-testimonials-demo-light-f539841b7a14221a.png
- Registry item: https://keyframeui.com/r/arc-scroll-testimonials-demo.json
- Categories: scroll, cards, motion
- Listed: 2026-10-14, source last changed 2026-10-15

## Install

```bash
npx shadcn@latest add https://keyframeui.com/r/arc-scroll-testimonials-demo.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- Registry items: `https://keyframeui.com/r/arc-scroll.json`

## Notes from the registry

Live preview, examples and props: https://keyframeui.com/components/arc-scroll#arc-scroll-testimonials-demo

## Files

### `registry/demos/arc-scroll-testimonials-demo.tsx`

```tsx
"use client";

import * as React from "react";

import { cn } from "@/lib/utils";
import { ArcScroll } from "@/components/ui/arc-scroll";

const testimonials = [
  {
    quote: "We replaced three libraries with it and our bundle got smaller. The motion feels hand-tuned.",
    name: "Maya Okafor",
    role: "Design Engineer, Parcel",
    avatar: "from-amber-300 to-rose-400",
  },
  {
    quote: "Our landing page finally feels alive. Copy, paste, adjust two props, ship.",
    name: "Daniel Reyes",
    role: "Founder, Fieldnote",
    avatar: "from-emerald-300 to-teal-500",
  },
  {
    quote: "Reduced motion support out of the box saved us a full accessibility pass.",
    name: "Priya Shah",
    role: "Frontend Lead, Halcyon",
    avatar: "from-sky-300 to-indigo-500",
  },
  {
    quote: "It reads like code I'd write myself, which is the highest compliment I have.",
    name: "Tom Lindqvist",
    role: "Staff Engineer, Orbit",
    avatar: "from-violet-300 to-fuchsia-500",
  },
  {
    quote: "Our designers stopped asking for After Effects exports. That alone was worth it.",
    name: "Aiko Tanaka",
    role: "Product Designer, Lumen",
    avatar: "from-lime-300 to-emerald-500",
  },
];

function initials(name: string) {
  return name
    .split(" ")
    .map((part) => part[0])
    .join("");
}

export function ArcScrollTestimonialsDemo() {
  const scrollRef = React.useRef<HTMLDivElement>(null);
  const [active, setActive] = React.useState(0);

  return (
    <div className="relative w-full overflow-hidden rounded-xl border border-zinc-200 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-950">
      <div ref={scrollRef} className="relative h-[var(--demo-height,32rem)] overflow-y-auto overscroll-contain">
        <ArcScroll
          containerRef={scrollRef}
          stageClassName="h-[var(--demo-height,32rem)]"
          spacing={30}
          tilt={false}
          scrollPerCard={300}
          onActiveChange={setActive}
          overlay={
            <>
              <p className="absolute inset-x-0 top-6 text-center font-mono text-xs uppercase tracking-widest text-zinc-500">
                Loved by teams who ship
              </p>
              <div className="absolute inset-x-0 bottom-8 flex justify-center gap-2">
                {testimonials.map((item, index) => (
                  <span
                    key={item.name}
                    className={cn(
                      "h-1.5 rounded-full transition-all duration-300",
                      index === active
                        ? "w-5 bg-zinc-900 dark:bg-zinc-50"
                        : "w-1.5 bg-zinc-300 dark:bg-zinc-700",
                    )}
                  />
                ))}
              </div>
            </>
          }
        >
          {testimonials.map((item) => (
            <figure
              key={item.name}
              className="flex h-52 w-64 flex-col justify-between rounded-2xl bg-white p-5 shadow-xl ring-1 shadow-zinc-900/10 ring-zinc-200 sm:w-72 dark:bg-zinc-900 dark:shadow-black/50 dark:ring-zinc-800"
            >
              <blockquote className="text-[15px] leading-relaxed text-zinc-700 dark:text-zinc-300">
                “{item.quote}”
              </blockquote>
              <figcaption className="flex items-center gap-3">
                <span
                  aria-hidden
                  className={`flex size-9 items-center justify-center rounded-full bg-linear-to-br text-xs font-semibold text-white ${item.avatar}`}
                >
                  {initials(item.name)}
                </span>
                <span className="text-sm">
                  <span className="block font-medium text-zinc-900 dark:text-zinc-50">
                    {item.name}
                  </span>
                  <span className="block text-zinc-500">{item.role}</span>
                </span>
              </figcaption>
            </figure>
          ))}
        </ArcScroll>
      </div>
    </div>
  );
}
```
