# Launch Dock: Testimonials

> Faces in a round dock. Each quote card grows out of the person who said it, and shrinks back into them when the next one opens.

A shadcn/ui block from the [@keyframeui](https://keyframeui.com/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/keyframeui/launch-dock-quotes-demo).

- Page, with a live preview: https://shadcn.exchange/r/keyframeui/launch-dock-quotes-demo
- Screenshot: https://shadcn.exchange/shot/keyframeui/launch-dock-quotes-demo/light.png
- Registry item: https://keyframeui.com/r/launch-dock-quotes-demo.json
- Categories: showcase, testimonials, motion
- Listed: 2026-11-02, source last changed 2026-11-02

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/keyframeui/launch-dock-quotes-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/launch-dock.json`

## Notes from the registry

Live preview, examples and props: https://keyframeui.com/components/launch-dock#launch-dock-quotes-demo

## Files

### `registry/demos/launch-dock-quotes-demo.tsx`

```tsx
"use client";

import { LaunchDock, type LaunchDockItem } from "@/components/ui/launch-dock";

const face = (id: string, size: number) =>
  `https://images.unsplash.com/photo-${id}?auto=format&fit=crop&crop=faces&w=${size}&h=${size}&q=80`;

const people = [
  {
    id: "ines",
    name: "Inês Moreau",
    role: "Partner, Atelier Sund",
    photo: "1438761681033-6461ffad8d80",
    quote: "Okto writes up our client calls before I'm back at my desk. I haven't typed a meeting note since March.",
  },
  {
    id: "rafael",
    name: "Rafael Santos",
    role: "Head of Sales, Kvarter",
    photo: "1507003211169-0a1dd7228f2d",
    quote: "Follow-ups used to cost us a day a week. Now they go out while the call is still fresh for everyone.",
  },
  {
    id: "mei",
    name: "Mei Tanaka",
    role: "Producer, Northwind Films",
    photo: "1534528741775-53994a69daeb",
    quote: "I search last month's calls like my inbox. “What did the client say about the ending?” Found in a second.",
  },
  {
    id: "luca",
    name: "Luca Bianchi",
    role: "Engineering Lead, Harbor",
    photo: "1500648767791-00dcc994a43e",
    quote: "Our standups are eight minutes now. Okto posts the decisions to the right channel, so nobody retells them.",
  },
  {
    id: "amara",
    name: "Amara Okafor",
    role: "Founder, Field & Fold",
    photo: "1531123897727-8f129e1688ce",
    quote: "The first tool my whole team adopted without a training session. It just shows up in the calls we already have.",
  },
  {
    id: "jonas",
    name: "Jonas Weber",
    role: "Brand Director, Tinta",
    photo: "1539571696357-5a69c17a67c6",
    quote: "It caught a budget number we'd all misheard on the call. That one note paid for the year.",
  },
];

function Quote({ person }: { person: (typeof people)[number] }) {
  return (
    <figure className="flex h-full flex-col justify-between p-11">
      <div>
        <svg viewBox="0 0 32 24" aria-hidden className="h-6 w-8 text-violet-500">
          <path
            fill="currentColor"
            d="M0 24V14.4C0 6.2 4.6 1.4 12.4 0l1.4 3.6C9.5 5 7.4 7.8 7.2 12H13v12H0Zm18.6 0V14.4C18.6 6.2 23.2 1.4 31 0l1 3.6C27.8 5 25.9 7.8 25.7 12h5.7v12H18.6Z"
          />
        </svg>
        <blockquote className="mt-6 text-[28px] leading-[1.36] font-medium tracking-[-0.018em] text-balance text-zinc-900 dark:text-zinc-50">
          {person.quote}
        </blockquote>
      </div>
      <figcaption className="flex items-center gap-3.5">
        {/* eslint-disable-next-line @next/next/no-img-element */}
        <img src={face(person.photo, 96)} alt="" className="size-11 rounded-full object-cover" />
        <div>
          <p className="text-[16px] font-semibold text-zinc-900 dark:text-zinc-50">{person.name}</p>
          <p className="text-[14px] text-zinc-500 dark:text-zinc-400">{person.role}</p>
        </div>
      </figcaption>
    </figure>
  );
}

const items: LaunchDockItem[] = people.map((person) => ({
  id: person.id,
  label: person.name,
  // eslint-disable-next-line @next/next/no-img-element
  icon: <img src={face(person.photo, 120)} alt="" />,
  content: <Quote person={person} />,
}));

export function LaunchDockQuotesDemo() {
  return (
    <div className="flex w-full max-w-xl flex-col items-center gap-8">
      <div className="text-center">
        <p className="text-sm font-medium text-violet-600 dark:text-violet-400">Okto for teams</p>
        <h2 className="mt-2 text-3xl font-semibold tracking-tight text-balance text-zinc-950 dark:text-white">
          Teams that stopped taking notes
        </h2>
      </div>
      <LaunchDock
        items={items}
        frame="card"
        fade={false}
        autoplay
        windowSize={[720, 360]}
        dockLabel="People quoted"
        className="[--launch-dock-roundness:0.5]"
      />
    </div>
  );
}
```
