Camera
A camera component for capturing photos with front/back camera toggle, mirror modes, and mask overlays.
Camera not active
Installation
CLI
npx shadcn@latest add "https://acfui.com/r/camera"pnpm dlx shadcn@latest add "https://acfui.com/r/camera"yarn dlx shadcn@latest add "https://acfui.com/r/camera"bun x shadcn@latest add "https://acfui.com/r/camera"Manual
Install the following dependencies:
npm install class-variance-authority lucide-reactpnpm add class-variance-authority lucide-reactyarn add class-variance-authority lucide-reactbun add class-variance-authority lucide-reactCopy and paste the following code into your project.
Usage
import { Camera } from "@/components/ui/camera"<Camera 
  onCapture={(imageData) => console.log('Photo captured:', imageData)}
  onError={(error) => console.error('Camera error:', error)}
/>Examples
Output Format Adapters
The Camera component supports different output formats to suit various use cases.
Base64 String Output (Default)
Perfect for immediate display, database storage, or simple image processing.
<Camera
  outputFormat="base64"
  captureFormat="image/jpeg"
  captureQuality={0.8}
  onCapture={(imageData) => {
    // imageData is a base64 string
    console.log('Base64 data:', imageData)
    // Example: "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD..."
    
    // Direct usage in img src
    setProfileImage(imageData)
  }}
/>File Object Output
Ideal for file uploads, FormData submissions, or when you need file metadata.
<Camera
  outputFormat="file"
  captureFormat="image/png"
  captureQuality={0.9}
  onCapture={(imageData) => {
    if (imageData instanceof File) {
      console.log('File object:', imageData)
      // Example file properties:
      // imageData.name: "photo-1703847234567.png"
      // imageData.size: 245760 (bytes)
      // imageData.type: "image/png"
      // imageData.lastModified: 1703847234567
      
      // Direct upload to server
      uploadFile(imageData)
    }
  }}
/>Complete Example with Both Formats
import { Camera } from "@/components/ui/camera"
import { useState } from "react"
export function CameraExample() {
  const [capturedData, setCapturedData] = useState<{
    type: "base64" | "file"
    data: string | File
    preview?: string
  } | null>(null)
  const onCaptureBase64 = (imageData: string | File) => {
    if (typeof imageData === "string") {
      setCapturedData({
        type: "base64",
        data: imageData,
        preview: imageData
      })
    }
  }
  const onCaptureFile = (imageData: string | File) => {
    if (imageData instanceof File) {
      // Convert File to base64 for preview
      const reader = new FileReader()
      reader.onload = (e) => {
        setCapturedData({
          type: "file",
          data: imageData,
          preview: e.target?.result as string
        })
      }
      reader.readAsDataURL(imageData)
    }
  }
  const uploadFile = async (file: File) => {
    const formData = new FormData()
    formData.append('image', file)
    try {
      const response = await fetch('/api/upload', {
        method: 'POST',
        body: formData
      })
      
      if (response.ok) {
        console.log('File uploaded successfully')
      }
    } catch (error) {
      console.error('Upload failed:', error)
    }
  }
  return (
    <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
      {/* Base64 Output */}
      <div>
        <h3>Base64 Output</h3>
        <Camera
          size="sm"
          outputFormat="base64"
          onCapture={onCaptureBase64}
        />
      </div>
      {/* File Output */}
      <div>
        <h3>File Output</h3>
        <Camera
          size="sm"
          outputFormat="file"
          onCapture={onCaptureFile}
        />
      </div>
      {/* Display Results */}
      {capturedData && (
        <div className="col-span-full">
          <h4>Captured {capturedData.type.toUpperCase()}</h4>
          {capturedData.preview && (
            <img src={capturedData.preview} alt="Captured" />
          )}
          <pre>{JSON.stringify({
            type: capturedData.type,
            ...(capturedData.data instanceof File ? {
              name: capturedData.data.name,
              size: capturedData.data.size,
              type: capturedData.data.type
            } : {
              length: capturedData.data.length
            })
          }, null, 2)}</pre>
        </div>
      )}
    </div>
  )
}Privacy & Camera Permissions
Enhanced camera permission handling with custom modal and cross-browser compatibility.
Permission Flow
The camera component includes an intelligent permission flow:
- Permission Check: Automatically detects if camera access is already granted
 - Custom Modal: Shows user-friendly explanation before browser prompt (only when needed)
 - Native Prompt: Triggers browser's permission dialog after user confirms
 - Direct Access: If already granted, skips modal and starts camera immediately
 
Cross-Browser Support
- Chrome/Edge: Uses Permissions API for reliable permission detection
 - Safari: Falls back to getUserMedia test for compatibility
 - Firefox: Full Permissions API support with enhanced error handling
 - Mobile Browsers: Optimized for touch interfaces and mobile permissions
 
Enhanced UX Features
- ✅ Smart Modal: Only shows when permission needed (not for returning users)
 - ✅ Clear Instructions: Step-by-step guidance for granting permissions
 - ✅ Recovery Flow: Easy retry mechanism for denied permissions
 - ✅ Privacy Badges: Visual indicators of data protection policies
 - ✅ Permission Status: Real-time display of current access level
 
Different Sizes
Choose from predefined size variants to fit your layout needs.
Default Size
Default Size
Standard camera size (320x240px) - perfect for most use cases
Camera not active
Small Size
Small Size
Compact camera size (256x192px) - ideal for thumbnails and tight spaces
Camera not active
Large Size
Large Size
Large camera size (384x288px) - great for detailed photo capture
Camera not active
Extra Large Size
Extra Large Size
Extra large camera size (448x320px) - perfect for high-quality captures
Camera not active
Aspect Ratios
Choose from different aspect ratios to fit your design requirements.
4:3 Traditional
4:3 Aspect Ratio
Traditional camera aspect ratio - ideal for standard photography
Camera not active
16:9 Widescreen
16:9 Aspect Ratio
Widescreen format - perfect for cinematic and landscape shots
Camera not active
1:1 Square
1:1 Aspect Ratio
Square format - ideal for profile photos and social media
Camera not active
Mirror Modes
Mirror mode allows you to flip the camera preview and/or the captured image.
No Mirror
No Mirror
Default behavior - no mirroring in preview or capture
Camera not active
Mirror All
Mirror All
Mirror both preview and captured image - useful for selfies
Camera not active
Mirror Preview Only
Mirror Preview Only
Mirror preview but capture normal image - best user experience
Camera not active
Mask Overlays
Add overlay masks to guide photo composition for specific use cases.
Square Mask
Square Mask
Perfect for profile photos and square avatars
Camera not active
Round Mask
Round Mask
Ideal for circular profile pictures and user avatars
Camera not active
Card Mask
Card Mask
Great for ID cards, licenses, and document photography
Camera not active
Manual Control
Camera not active
Custom Capture Handler
Privacy & Security
Enhanced Camera Permission Flow
The camera component features an intelligent, privacy-first permission system:
Smart Permission Detection
// Automatic permission checking on component mount
useEffect(() => {
  checkCameraPermission() // Multi-method browser detection
}, [])Custom Modal Before Native Prompt
- When needed: Shows custom explanation modal before browser prompt
 - When not needed: Direct camera access for returning users with permissions
 - Cross-browser: Works consistently across Chrome, Safari, Firefox, Edge
 
Permission States Handled
checking: Detecting current permission statusprompt: Shows custom modal → triggers native browser promptgranted: Direct camera access, no modal neededdenied: Clear recovery instructions with retry mechanism
Camera Access Permission Flow
The camera component includes comprehensive permission handling with privacy-first approach:
- Clear Permission Requests: Custom modal explains camera usage before native prompt
 - Privacy Information: Transparent communication about data handling
 - Error Recovery: Graceful handling of permission denied states with retry options
 - Local Processing: All camera processing happens client-side only
 
Privacy Features
- 🔒 Local Processing: No data sent to servers
 - 🚫 No Recording: Only captures individual photos
 - 👁️ Transparent Usage: Clear privacy information displayed
 - ⚡ Instant Revocation: Users can disable camera access anytime
 - 🛡️ Secure by Default: HTTPS required in production
 - 🎯 Smart UX: Modal only shows when permission actually needed
 
// Enhanced privacy-focused camera usage
<Camera
  onStart={() => console.log('Camera started with user consent')}
  onError={(error) => {
    // Handle permission denied gracefully with recovery options
    if (error.includes('Permission denied')) {
      showPermissionRecoveryFlow()
    }
  }}
  autoStart={false} // Requires explicit user action
/>API Reference
Camera
| Prop | Type | Default | Description | 
|---|---|---|---|
size | "sm" | "md" | "lg" | "xl" | "full" | "md" | Size variant of the camera component | 
aspect | "4:3" | "16:9" | "1:1" | "4:3" | Aspect ratio of the camera | 
onCapture | (imageData: string | File) => void | - | Callback when a photo is captured | 
onError | (error: string) => void | - | Callback when an error occurs | 
onStart | () => void | - | Callback when camera starts | 
autoStart | boolean | true | Whether to automatically start the camera | 
showControls | boolean | true | Whether to show camera controls | 
allowDownload | boolean | true | Whether to allow downloading captured photos | 
silent | boolean | false | Whether to disable shutter sound | 
mirror | boolean | "preview" | false | Mirror mode: true (all), "preview" (preview only), false (none) | 
mask | "none" | "square" | "round" | "card" | "none" | Overlay mask for guided composition | 
outputFormat | "base64" | "file" | "base64" | Output format: base64 string or File object | 
captureFormat | "image/jpeg" | "image/png" | "image/webp" | "image/jpeg" | Image format for capture | 
captureQuality | number | 0.9 | Image quality (0.0 to 1.0) | 
Output Format Comparison
| Format | Type | Use Cases | Pros | Cons | 
|---|---|---|---|---|
base64 | string | Display, database storage, simple processing | Immediate usage, no conversion needed | Large string size, not ideal for uploads | 
file | File | File uploads, FormData, server submissions | Proper file handling, metadata included | Requires conversion for display | 
Example Values
Base64 Output
// Example base64 string (truncated)
"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k="
// Length: typically 50,000-200,000 charactersFile Output
// Example File object properties
{
  name: "photo-1703847234567.png",
  size: 245760,
  type: "image/png", 
  lastModified: 1703847234567,
  // Methods: stream(), text(), arrayBuffer(), etc.
}Size Variants
| Size | Dimensions | Use Case | 
|---|---|---|
sm | 256×192px | Thumbnails, compact spaces | 
md | 320×240px | Default, most use cases | 
lg | 384×288px | Detailed captures | 
xl | 448×320px | High-quality photos | 
full | 100% container | Full-screen camera | 
Browser Support
- Chrome: ✅ Full support
 - Firefox: ✅ Full support
 - Safari: ✅ Full support (iOS 11+)
 - Edge: ✅ Full support
 
Note: Camera access requires HTTPS in production environments and user permission.
Accessibility
- Proper ARIA labels for camera controls
 - Keyboard navigation support
 - Screen reader announcements for state changes
 - High contrast mode compatibility
 
Keyboard Interactions
| Key | Description | 
|---|---|
| SpaceEnter | When focus is on the capture button, takes a photo. | 
| Tab | Moves focus between camera controls (capture, toggle, sound, stop). | 
| Escape | Stops the camera when active. | 
Examples in Production
Profile Photo Capture
<Camera
  mask="square"
  aspect="1:1"
  mirror="preview"
  onCapture={(imageData) => {
    // Upload to your backend
    uploadProfilePhoto(imageData)
  }}
/>ID Card Scanning
<Camera
  mask="card"
  aspect="16:9"
  captureQuality={1.0}
  onCapture={(imageData) => {
    // Process ID card
    processIDCard(imageData)
  }}
/>Avatar Creation
<Camera
  mask="round"
  aspect="1:1"
  size="sm"
  onCapture={(imageData) => {
    // Create avatar
    createAvatar(imageData)
  }}
/>