shadcn.io is not affiliated with official shadcn/ui
Video Testimonials Dialog Grid Block for React Aria
Five customer-story videos on shared hairlines, one large lead beside four smaller; each poster is drawn from UI with a play pill, and opens a player dialog with the speaker and a timed transcript.
Installation
Overview
Let customers tell their own story, and let visitors read it too. A compact header about Calder, a made-up internal developer platform, holds the Shine Button. Below it a band of five cells shares its hairlines: a large lead story spanning two columns and two rows, with a few of its timed transcript lines as a teaser, and four smaller ones. Each cell has a 16:9 poster drawn from UI (a head and shoulders with the speaker's initials, a scrubber and a play pill showing the story's length), a pull quote and the speaker's name and role. On a phone the five cells become a snap carousel with the next one showing at the edge. Opening a poster shows a dialog with the speaker's name and company, the player frame and a transcript of three timed lines, so the story works for people who can't or don't want to press play. Nothing plays here: the posters and the player frame are placeholders, and the dialog says so. The page follows the theme; only the player frame inside the dialog is dark, as a video player is by nature. It's built from Dialog, Aspect Ratio, Grid Pattern, Avatar, Shine Button and Guides, with Lucide's play mark, CSS entrances that stay still under reduced motion and your theme's tokens. For landing pages, case-study shelves and pricing pages where recorded customers carry more weight than a paragraph of quotes.
FAQ
How do I put a real video in the dialog?
Replace the aria-hidden AspectRatio under the dialog's header in StoryDialog (the grid and the play mark) with your player. The placeholder frame carries its own dark scope, because a video player is dark by nature, in a light theme too; the rest of the dialog follows the theme. For a file you host, use a <video controls> with className="size-full", a <track kind="captions"> and autoPlay, since the visitor already asked for it. For a hosted embed, use its iframe with className="size-full", allow="autoplay; fullscreen" and a title naming the story. The dialog only mounts its content while open, so closing it stops the video. Then remove the "Demo player" sentence from the dialog's description.
How do I show a frame of each video as its thumbnail?
In Thumbnail, replace the GridPattern and the drawn head and shoulders with an img of a frame, using className="absolute inset-0 size-full object-cover" and alt="": the play button already names the speaker, so the picture needn't be read twice. Keep the play pill; its colours come from your theme and stay readable over a photo because it sits on its own bg-background pill. Set each story's length to the real running time.
Why does each story have a transcript?
A transcript lets people read a story in a quiet office or on a slow connection, and it is how screen reader users and people who are deaf or hard of hearing get the same content. Each story has typed transcript lines with a time and text; replace them with your own captions, or load the full transcript from your CMS. The pull quote on the card is one of the transcript's lines.
How do keyboard and screen reader users open and close a story?
Each thumbnail is one button, named "Play Odalys Whitmore's story, 2:34", reached with Tab and pressed with Enter or Space. The dialog moves focus inside, keeps it there and is named by its title (the speaker's name, role and company), with the description read after it. Escape, the close button or a click outside closes it, and focus returns to the thumbnail you opened. The quotes are figure elements with a blockquote and a figcaption, in a list of five.
On React Aria, the thumbnail is React Aria's own Button inside DialogTrigger, which opens the Dialog beside it.
How does the grid fit a phone?
On phones the stories scroll sideways in a snap carousel, each card 85% of the width so the next shows; from md they sit in a grid, two columns and then four from lg with the lead story spanning two by two. Every poster keeps its 16:9 shape at any width. The dialog stays centred with a margin on each side instead of sliding up from the bottom, so the player keeps its shape, and it scrolls on its own if a short screen can't fit the player and the transcript.
Does it work in right-to-left languages?
Yes. The quotes and transcript lines sit in bdi, so their punctuation and times stay where they belong, the play pill and the transcript's time column follow the reading direction, and the play mark keeps pointing forward, as media controls do.
How do I point the button at my page?
Change the href of the call to action in the header. Say where it goes in its label, such as "Browse all customer stories". It's a Shine Button, the one primary control in the block, and a diagonal band of light crosses it once on hover or focus (it fades in instead with reduced motion).
On React Aria, the button takes the URL as its href and renders an a.