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.
registry/demos/launch-dock-quotes-demo.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>
);
}
Live preview, examples and props: https://keyframeui.com/components/launch-dock#launch-dock-quotes-demo