shadcn.io is not affiliated with official shadcn/ui
QR Scanner Dialog Block for React Aria
An event check-in page with a simulated scanner dialog shown open: a viewfinder around a real sample QR code, a Wi-Fi, link and ticket payload read in turn, a result card that names the kind and cuts it into parts, a typed-code tab and a recent-scans strip.
Installation
Overview
Read what a code holds before anything acts on it, with this scanner dialog for React with React Aria. It opens from the Check-in page of Stubfold, an event check-in app: the page's header holds an outline Scan ticket Button, and the page, a count and a list of five guests, is drawn under a soft scrim inside a hairline frame with a plus mark on each corner. The scanner is a simulation and says so on screen: no camera is used, no microphone, no permission is asked. In the Dialog a Tabs pair offers Camera, a viewfinder with four corner marks and a scan line around a real QR Code of a typed sample, and Enter code, a field for a pasted payload. Scan the sample code runs a short pass, with Stop, and takes three samples in turn: a link, a Wi-Fi code and a ticket. The result card names the kind in a Badge, Link, Wi-Fi, Ticket or Text, and cuts the payload into its parts, with a Copy Button on the host and the password. Check in Ada is the only Shine Button: it puts a ticket on the guest list as real text. For preview the dialog is drawn open in place when the page loads; the header button opens the real Dialog.
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 page: a native dialog element that isn't modal, so nothing is trapped, hidden or focused, and the page around the preview keeps working. The Scan ticket 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 ScanFields component and share the block's state, so a scan made in the copy is there 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.
Is a camera used, and is the QR code real?
No camera is opened, no microphone and no permission prompt, and the dialog says so under its title. The viewfinder is a drawing, and the code in it is a sample typed in the block. That code is a real QR code, drawn by the catalog component, so a phone pointed at the screen reads the same text the demo does. In your app read frames from a camera stream with a decoder and pass the decoded text to keep; everything after that, the reading, the card and the strip, stays as it is.
How does it tell the kinds apart?
readingOf looks at the text. A code that starts with WIFI: is a Wi-Fi code, cut at its semicolons into network, security and password. A web address becomes a link with its host and path. A line shaped like "Ticket 0417: Name, Admission" is a ticket, and anything else is plain text. A link is never followed: Open link says "Not opened in this demo", and a real app should show the host first and ask. A ticket that is not on the list, or is already in, says so and leaves the list alone.
What does the scan do, and can I stop it?
Scan the sample code starts a pass of a little over a second: the line sweeps down once, then the brackets close in on the code and the result card fills. Stop ends the pass early and nothing is read; it is always on the page and quiet until a pass runs. Under reduced motion the line holds still in the middle and the code is read at once. The next press takes the next sample, so the link, the Wi-Fi code and the ticket come round in turn. A polite message says what was read.
What stays local in the demo, and how do I wire it up?
Everything. The samples, the guest list and the recent scans are typed constants, the times are minutes on a typed clock, and checking a guest in changes only this page's state. In your app load the guests from your API, send the check-in from finish, and keep the guest's own record as the source for the strip.
Is it accessible from the keyboard?
Yes. The real dialog is named Scan a ticket, focus starts in the dialog and stays inside until it closes, and Escape and the close button return focus to Scan ticket. The two tabs move with the arrow keys, Enter in the code field reads it, and each recent scan is a button with a full name such as "Ticket, 0388, scanned 10:31". The button and the dialog sit inside DialogTrigger, controlled with isOpen and onOpenChange, React Aria focuses the dialog itself so a frame later focus moves to the first control, and the dialog title is a level 2 heading. The copy shown on load moves no focus; Escape inside it and its close button close it and put focus on the button.