Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Testimonial With Portrait Drop Zone Block for Radix UI

The portrait slot beside a customer quote is a working drop zone: drop or choose a photo and it fills the tile, with a button to remove it.

Framework
UI library

Preview

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Overview

Show a quote with a face before you have the photo. A short header sits above a split band between the page's rails: a drop zone on one side, a large quote on the other, with a divider between them whose + mark lands on the band's top rule. The drop zone is a framed figure with + marks on its corners: the customer's initials in a circle inside two hairline rings, "Drop a portrait here" and "or press to choose a file". Drop an image on it, or press it to choose a file, and the photo fills the tile so you can judge how it sits beside the quote. A "Remove photo" button puts the initials back. Nothing is uploaded: the file is previewed in the browser and never sent anywhere. Beside the photo, the quote leads, the customer's name and role follow, and a Shine Button links to the full story (it's the block's only primary control). It's built from File Upload, Guides, Avatar, Button and Shine Button, with CSS entrances that stay still under reduced motion and your theme's tokens. For design reviews, page builders and template previews, where visitors or editors try their own portrait before committing to one.

FAQ

Does it upload the photo anywhere?

No. The block uses the browser's own file handling: the chosen image is shown through an object URL that exists only in the visitor's tab, and it is released again when the preview is removed or the page closes. To keep the photo, read the file from the onFileChange handler's acceptedFiles and send it to your storage there.

Which files does it accept, and what if someone drops the wrong one?

One image at a time (accept="image/*" and maxFiles={1}); dropping a second image replaces the first. A file that isn't an image is turned away (a photo already in the tile stays) and the status line under the tile says "That file isn’t an image. Choose a PNG, JPG or WebP photo." Add maxFileSize to the FileUpload to cap the size, and extend the message to match.

How do keyboard and screen reader users use it?

The whole tile is one focusable drop zone named by its text, "Drop a portrait here, or press to choose a file". Enter or Space opens the file picker, so dragging is never required. Choosing a photo, rejecting a file and removing the photo are each announced politely by the status line under the tile, and the status is empty on load. After "Remove photo", focus returns to the tile, since the button hides itself when there is no photo. The decorative rings and initials are hidden from assistive technology.

How is the preview drawn?

The chosen file is laid over the drop zone as an image that fills the tile, with pointer events switched off so another drop still lands on the zone underneath; dropping a new photo replaces the old one. The tile keeps its shape (4:5 from lg, wide on smaller screens) with or without a photo, so nothing on the page shifts. The focus ring sits on the tile's frame, so it shows even when a photo covers the zone.

How do I swap the placeholder for a fixed photo in production?

Delete the FileUpload and render an image in the same aspect-4/5 frame with className="size-full rounded-xl object-cover" and an alt if the picture carries information the caption doesn't. The quote, the caption and the link beside it don't change.

How does it fit a phone, and a right-to-left page?

Below the lg breakpoint the drop zone becomes a short, wide tile above the quote, with a hairline between them, so the quote is still on the first screen; nothing scrolls sideways at 320 px. In right-to-left languages the tile and quote swap sides and the divider moves with them, and the quote's quotation marks stay at its ends through bdi.