Not affiliated with official shadcn/ui. Visit ui.shadcn.com for official docs.
Unlock this block—Get Pro at 60% offReact Dialog Block QR Scanner
QR code scanner dialog with camera viewfinder, manual code input, scan history, and action handling for URLs and text
Looking to implement shadcn/ui blocks?
Join our Discord community for help from other developers.
Scan codes in seconds. This React QR code scanner dialog provides camera-based scanning with animated viewfinder, manual code entry fallback, recent scan history, and smart action handling for URLs and text content. Built with shadcn/ui Dialog, Button, Input, Tabs, ScrollArea, and Badge components using Tailwind CSS, users scan or enter codes with immediate result preview and quick actions. Camera viewfinder, manual input, scan history—perfect for mobile apps, inventory systems, event check-ins, or any Next.js application where QR code scanning enables fast, contactless data capture.
"use client";import { useState } from "react";import { QrCode, Camera, Keyboard, Clock, Link, FileText, Copy, ExternalLink, Trash2, Check, ScanLine, X,} from "lucide-react";import { Button } from "@/components/ui/button";import { Badge } from "@/components/ui/badge";import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger,} from "@/components/ui/dialog";import { Input } from "@/components/ui/input";import { Label } from "@/components/ui/label";import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";import { ScrollArea } from "@/components/ui/scroll-area";import { Separator } from "@/components/ui/separator";import { cn } from "@/lib/utils";type ScanResult = { id: string; content: string; type: "url" | "text"; timestamp: Date;};const mockHistory: ScanResult[] = [ { id: "1", content: "https://example.com/product/12345", type: "url", timestamp: new Date(Date.now() - 1000 * 60 * 5), }, { id: "2", content: "WIFI:T:WPA;S:MyNetwork;P:password123;;", type: "text", timestamp: new Date(Date.now() - 1000 * 60 * 60), }, { id: "3", content: "https://docs.example.com/guide", type: "url", timestamp: new Date(Date.now() - 1000 * 60 * 60 * 24), },];export const title = "React Dialog Block QR Scanner";export default function DialogQrScanner() { const [open, setOpen] = useState(false); const [isScanning, setIsScanning] = useState(false); const [manualInput, setManualInput] = useState(""); const [scanResult, setScanResult] = useState<ScanResult | null>(null); const [history, setHistory] = useState<ScanResult[]>(mockHistory); const [copied, setCopied] = useState(false); const detectContentType = (content: string): "url" | "text" => { try { new URL(content); return "url"; } catch { return "text"; } }; const formatTimestamp = (date: Date) => { const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / (1000 * 60)); const diffHours = Math.floor(diffMs / (1000 * 60 * 60)); const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); if (diffMins < 1) return "Just now"; if (diffMins < 60) return `${diffMins}m ago`; if (diffHours < 24) return `${diffHours}h ago`; return `${diffDays}d ago`; }; const startScanning = () => { setIsScanning(true); // Simulate scanning delay setTimeout(() => { const mockScannedContent = "https://example.com/scanned-code-" + Math.random().toString(36).substr(2, 6); handleScanComplete(mockScannedContent); }, 2000); }; const stopScanning = () => { setIsScanning(false); }; const handleScanComplete = (content: string) => { setIsScanning(false); const result: ScanResult = { id: Math.random().toString(36).substr(2, 9), content, type: detectContentType(content), timestamp: new Date(), }; setScanResult(result); setHistory([result, ...history.slice(0, 9)]); }; const handleManualSubmit = () => { if (!manualInput.trim()) return; handleScanComplete(manualInput.trim()); setManualInput(""); }; const handleHistorySelect = (item: ScanResult) => { setScanResult(item); }; const handleCopy = () => { if (scanResult) { navigator.clipboard.writeText(scanResult.content); setCopied(true); setTimeout(() => setCopied(false), 1500); } }; const handleOpenUrl = () => { if (scanResult?.type === "url") { window.open(scanResult.content, "_blank"); } }; const clearHistory = () => { setHistory([]); }; const handleClose = () => { setOpen(false); setTimeout(() => { setScanResult(null); setManualInput(""); setIsScanning(false); setCopied(false); }, 200); }; return ( <Dialog open={open} onOpenChange={setOpen}> <div className="flex min-h-[350px] items-center justify-center"> <DialogTrigger asChild> <Button variant="outline"> <QrCode className="mr-2 h-4 w-4" /> Scan QR Code </Button> </DialogTrigger> </div> <DialogContent className="sm:max-w-md"> <DialogHeader> <DialogTitle className="flex items-center gap-2"> <QrCode className="h-5 w-5" /> QR Code Scanner </DialogTitle> <DialogDescription> Scan a QR code with your camera or enter it manually. </DialogDescription> </DialogHeader> {scanResult ? ( <div className="space-y-4 py-4"> <div className="rounded-lg border p-4"> <div className="flex items-start gap-3"> <div className={cn( "flex h-10 w-10 items-center justify-center rounded-full shrink-0", scanResult.type === "url" ? "bg-primary/10" : "bg-muted" )}> {scanResult.type === "url" ? ( <Link className="h-5 w-5 text-primary" /> ) : ( <FileText className="h-5 w-5 text-muted-foreground" /> )} </div> <div className="flex-1 min-w-0"> <div className="flex items-center gap-2 mb-1"> <Badge variant="secondary" className="text-xs"> {scanResult.type === "url" ? "URL" : "Text"} </Badge> <span className="text-xs text-muted-foreground"> {formatTimestamp(scanResult.timestamp)} </span> </div> <p className="text-sm font-mono break-all"> {scanResult.content} </p> </div> </div> </div> <div className="flex gap-2"> <Button variant="outline" className="flex-1" onClick={handleCopy} > {copied ? ( <Check className="mr-2 h-4 w-4 text-primary" /> ) : ( <Copy className="mr-2 h-4 w-4" /> )} {copied ? "Copied!" : "Copy"} </Button> {scanResult.type === "url" && ( <Button className="flex-1" onClick={handleOpenUrl}> <ExternalLink className="mr-2 h-4 w-4" /> Open URL </Button> )} </div> <Button variant="outline" className="w-full" onClick={() => setScanResult(null)} > Scan Another </Button> </div> ) : ( <Tabs defaultValue="camera" className="w-full"> <TabsList className="grid w-full grid-cols-3"> <TabsTrigger value="camera"> <Camera className="mr-2 h-4 w-4" /> Camera </TabsTrigger> <TabsTrigger value="manual"> <Keyboard className="mr-2 h-4 w-4" /> Manual </TabsTrigger> <TabsTrigger value="history"> <Clock className="mr-2 h-4 w-4" /> History </TabsTrigger> </TabsList> <TabsContent value="camera" className="space-y-4 mt-4"> <div className="relative aspect-square w-full rounded-lg border-2 border-dashed bg-muted/30 overflow-hidden"> {isScanning ? ( <> <div className="absolute inset-0 bg-gradient-to-b from-transparent via-primary/10 to-transparent animate-scan" /> <div className="absolute inset-8 border-2 border-primary rounded-lg" /> <div className="absolute inset-0 flex items-center justify-center"> <ScanLine className="h-12 w-12 text-primary animate-pulse" /> </div> <Button variant="secondary" size="sm" className="absolute bottom-4 right-4" onClick={stopScanning} > <X className="mr-2 h-4 w-4" /> Cancel </Button> </> ) : ( <div className="absolute inset-0 flex flex-col items-center justify-center gap-4"> <QrCode className="h-12 w-12 text-muted-foreground" /> <p className="text-sm text-muted-foreground text-center px-4"> Position the QR code within the frame to scan </p> <Button onClick={startScanning}> <Camera className="mr-2 h-4 w-4" /> Start Scanning </Button> </div> )} </div> <p className="text-xs text-center text-muted-foreground"> Camera access is required for scanning </p> </TabsContent> <TabsContent value="manual" className="space-y-4 mt-4"> <div className="space-y-2"> <Label htmlFor="manual-code">Enter code manually</Label> <Input id="manual-code" placeholder="Paste or type the code content..." value={manualInput} onChange={(e) => setManualInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && handleManualSubmit()} /> <p className="text-xs text-muted-foreground"> Enter the URL or text content from the QR code </p> </div> <Button className="w-full" onClick={handleManualSubmit} disabled={!manualInput.trim()} > Submit Code </Button> </TabsContent> <TabsContent value="history" className="mt-4"> {history.length > 0 ? ( <> <div className="flex items-center justify-between mb-2"> <span className="text-sm font-medium">Recent Scans</span> <Button variant="ghost" size="sm" className="h-6 text-xs text-destructive hover:text-destructive" onClick={clearHistory} > <Trash2 className="mr-1 h-3 w-3" /> Clear </Button> </div> <ScrollArea className="h-[200px]"> <div className="space-y-2"> {history.map((item) => ( <button key={item.id} onClick={() => handleHistorySelect(item)} className="w-full rounded-lg border p-3 text-left hover:bg-muted/50 transition-colors" > <div className="flex items-center gap-2 mb-1"> {item.type === "url" ? ( <Link className="h-3 w-3 text-primary" /> ) : ( <FileText className="h-3 w-3 text-muted-foreground" /> )} <Badge variant="secondary" className="text-xs"> {item.type} </Badge> <span className="text-xs text-muted-foreground ml-auto"> {formatTimestamp(item.timestamp)} </span> </div> <p className="text-sm truncate font-mono"> {item.content} </p> </button> ))} </div> </ScrollArea> </> ) : ( <div className="flex flex-col items-center justify-center py-8 text-center"> <Clock className="h-8 w-8 text-muted-foreground mb-2" /> <p className="text-sm font-medium">No scan history</p> <p className="text-xs text-muted-foreground"> Your recent scans will appear here </p> </div> )} </TabsContent> </Tabs> )} {!scanResult && ( <DialogFooter> <Button variant="outline" onClick={handleClose}> Cancel </Button> </DialogFooter> )} </DialogContent> <style jsx>{` @keyframes scan { 0%, 100% { transform: translateY(-100%); } 50% { transform: translateY(100%); } } .animate-scan { animation: scan 2s ease-in-out infinite; } `}</style> </Dialog> );}Installation
npx shadcn@latest add https://www.shadcn.io/registry/dialog-qr-scanner.jsonnpx shadcn@latest add https://www.shadcn.io/registry/dialog-qr-scanner.jsonpnpm dlx shadcn@latest add https://www.shadcn.io/registry/dialog-qr-scanner.jsonbunx shadcn@latest add https://www.shadcn.io/registry/dialog-qr-scanner.jsonRelated blocks you will also like
React Dialog Block Data Import
Data import interface
React Dialog Block Contact Card
Contact scanning
React Dialog Block Share Link
QR code generation
React Dialog Block Device Management
Device pairing
React Dialog Block Success Confirmation
Scan confirmation
React Dialog Block API Key
Code entry
Questions you might have
React Dialog Block Project Initializer
Project initialization dialog with numbered steps, framework selection, package manager, linter configuration, and testing tool options
React Dialog Block Quick Actions
Quick actions dialog with searchable action list, keyboard shortcuts, recent actions, and categorized commands