Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Camera Capture File Upload Block for React Aria

A site-inspection report with its camera dialog shown open: a drawn viewfinder and shutter, a Quality select that sets the size, Retake, and Use photo, which adds a named chip to the report behind.

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

Let people take a photo straight into a form, with this camera capture dialog for React with React Aria. It opens from the report page of Fieldshot, a site-inspection app: the page's header holds an outline Button labelled Take a photo, and the report is drawn under a soft scrim inside a hairline frame with a plus mark on each corner, its Photos field holding one chip per photo. The dialog holds a drawn viewfinder (a wall, a pipe, a crack, a thirds grid and a focus bracket), a Native Select for Quality (Standard 1.2 MB, High 3.4 MB, Original 6.8 MB) that sets the size line, and Retake. The one Shine Button is the shutter: Take photo freezes a drawn frame labelled "Photo 1", then it reads Use photo and adds a chip such as "capture-1.jpg, 3.4 MB" to the report and closes. Each chip has a Remove button. No camera is opened and no file is made. For preview the dialog is drawn open in place when the page loads; the header button opens the real Dialog, with the same viewfinder and the same state. For inspection, claims and field-service apps.

FAQ

Why is the dialog already open, and what do I keep?

So you can see it without a click. On load the dialog is a copy drawn in place over the report: a native dialog element that isn't modal, so nothing is trapped, hidden or focused. The header button opens the real Dialog, modal, with its focus trap, Escape and focus return, and the copy stays closed until a reload. Both show the same CaptureFields component and share the block's state, so a frame taken in the copy is frozen in the real one. In your app keep the Dialog and drop the copy: the "in-place" state, the scrim and the dialog element inside the frame.

Does it open the camera?

No. The viewfinder is a drawing and the shutter freezes a drawn frame; no permission is asked and no file exists. To wire it, call navigator.mediaDevices.getUserMedia from the shutter's handler, draw a video frame to a canvas and make a file from it with toBlob, then hand that to your upload. On devices with a camera, an input with capture is the simpler route.

What does Quality change?

The size line only: "Photo 1 will be about 3.4 MB", then "capture-1.jpg, 3.4 MB" once frozen. The sizes are typed. In your app, set the canvas size or the JPEG quality from it.

Is it accessible from the keyboard?

Yes. The real dialog is named by its title and described by its sentence, focus starts inside and stays there until it closes, and Escape or Cancel returns it to the Take a photo button. The button and the dialog sit inside DialogTrigger, controlled with isOpen and onOpenChange, and the title is a level 2 heading. The copy shown on load moves no focus; Escape inside it, Cancel and its close button close it and put focus on the button. One polite status message reports each step.

What stays local?

All of it. Chips are names and typed sizes in the block's state; a reload clears them. The report holds six photos and says so when it is full.

How does it look on a phone?

The copy shown on load docks to the foot of the page as a bottom sheet, its actions pinned with Take photo or Use photo on top, and the viewfinder and fields scroll above them. The real dialog keeps the Dialog's own phone layout: a sheet stuck to the bottom of the screen.