A rotating testimonial: a portrait on the left and a large serif quote on the right blur out and in together every six seconds, pause on hover, and can be chosen directly from a row of round portrait buttons, with a pill action beneath.
Three voices in rotation: a portrait cross-fades beside a quote that settles in word by word, on a six-second timer that pauses while the pointer is over the section, with portrait buttons to choose directly.
Upgrade to Pro
Pro blocks install through the shadcn CLI with your license key and ship their full source. Docs and live previews stay open to everyone, so you can read every block's details first.
Swap YOUR_EMAIL and YOUR_KEY for the email and license key on your account. Find your license key on your account page.
Radix flavor
npx shadcn add "https://ui.beste.co/r/testimonial50?email=YOUR_EMAIL&license_key=YOUR_KEY"Base UI flavor
npx shadcn add "https://ui.beste.co/r-base/testimonial50?email=YOUR_EMAIL&license_key=YOUR_KEY"This installs the block to components/beste/block/testimonial50.tsx plus the button22 sliding-marker pill and the text1 word stagger it uses.
The installed file exports testimonial50Demo alongside the block: the exact props behind the preview above. Spread it to get a working testimonial carousel in one line.
import { Testimonial50, testimonial50Demo } from "@/components/beste/block/testimonial50";
export default function Page() {
return <Testimonial50 {...testimonial50Demo} />;
}Then replace the demo with your own props. Written out, a trimmed setup looks like this:
import { Testimonial50 } from "@/components/beste/block/testimonial50";
export default function Page() {
return (
<Testimonial50
eyebrow="Three voices"
voices={[
{
quote: "I arrived wanting to be told what to do. I left knowing that nobody could.",
name: "Sharon Van Etten",
context: "Individual therapy, fourteen months",
image: { src: "/people/sharon.jpg", alt: "Portrait of a client" },
},
]}
button={{ label: "Read all stories", href: "/stories" }}
/>
);
}| Prop | Type | Default | Description |
|---|---|---|---|
eyebrow | string | – | Small-caps line above the quote |
voices | Voice[] | [] | The voices; the first is shown on mount |
button | ActionButton | – | Pill action under the portrait buttons |
className | string | – | Extra classes for the outer <section> |
type Voice = {
quote: string;
name: string;
context: string;
image: { src: string; alt: string };
};
type ActionButton = {
label: string;
href: string;
tone?: "primary" | "light" | "dark" | "outline";
};min-h-[14rem] rising to min-h-[18rem], which reserves the height so a short quote following a long one does not make the page jump.text1 with trigger="mount" inside AnimatePresence, so every rotation re-plays the word stagger rather than swapping the text in one frame.0.3 + quote.split(" ").length * 0.035, the same arithmetic as the stagger, so it lands as the last word of that particular quote settles.aria-pressed and an aria-label with the person's name, while their images use an empty alt, since the button already has its accessible name.testimonial53
One long testimonial beside a portrait: the photograph drifts inside its frame on the left while a serif quote settles in word by word on the right, closed by a ruled row with the speaker's name, context and a sliding-marker pill.
testimonial51
One centred testimonial: a long serif quote that settles in word by word, the speaker's portrait, name and context beneath it, and a sliding-marker pill.
testimonial42
A testimonial grid with an eyebrow over a hairline rule, a two-column heading, and three bordered quote cards each closing with an author avatar, name, and role.
testimonial48
Long-form testimonials told as stories: a serif heading settling in word by word beside the intro and a pill action, then alternating rows that pair a drifting portrait photograph with a story title, a serif quote, the person, and a link to the full account.