Dots

Camera Frame

A dark camera overlay with a document, face or QR frame, a hint and capture actions.

Photo of your ID

Front of your ID card

Fit all four corners inside the frame

"use client"

import * as React from "react"

The CameraFrame component is the screen around a live camera picture: photographing an ID for verification, a selfie check, or scanning a QR code to pay. It draws the frame, the dimmed surround, the hint and the capture buttons, and stays dark in both themes. It never touches the camera itself: you render your own <video> or <img> in CameraFrameMedia. To let the user pick an existing photo or file instead, use FileTrigger.

Installation

pnpm dlx shadcn@latest add @dots/camera-frame

Usage

import {
  CameraFrame,
  CameraFrameActions,
  CameraFrameHeader,
  CameraFrameHint,
  CameraFrameHintDescription,
  CameraFrameHintTitle,
  CameraFrameMedia,
  CameraFrameShutter,
  CameraFrameTitle,
  CameraFrameViewfinder,
} from "@/components/ui/camera-frame"
<CameraFrame aria-label="ID photo">
  <CameraFrameMedia>
    <video ref={videoRef} autoPlay playsInline muted />
  </CameraFrameMedia>
  <CameraFrameHeader>
    <CameraFrameTitle>Photo of your ID</CameraFrameTitle>
  </CameraFrameHeader>
  <CameraFrameViewfinder shape="document" />
  <CameraFrameHint>
    <CameraFrameHintTitle>Front of your ID card</CameraFrameHintTitle>
    <CameraFrameHintDescription>
      Fit all four corners inside the frame
    </CameraFrameHintDescription>
  </CameraFrameHint>
  <CameraFrameActions>
    <CameraFrameShutter onPress={takePhoto} />
  </CameraFrameActions>
</CameraFrame>

Composition

Use the following composition to build a camera frame:

CameraFrame
├── CameraFrameMedia
├── CameraFrameHeader
│   └── CameraFrameTitle
├── CameraFrameViewfinder
├── CameraFrameHint
│   ├── CameraFrameHintTitle
│   └── CameraFrameHintDescription
└── CameraFrameActions
    ├── CameraFrameAction
    ├── CameraFrameShutter
    └── CameraFrameTorch

Features

  • Dark in both themes: the root scopes the dark tokens to itself
  • screen fills its container edge to edge for a full-screen capture step. inset is a rounded, fixed-height panel for use inside a page, as in the demos here
  • Document, oval and square frames with a dimmed surround
  • A scan line for QR codes, a progress arc for face checks, and found and error states
  • A hint that is read out politely whenever it changes
  • A round shutter, round icon actions and a flashlight toggle
  • Any direct child, such as a plain Button, is lifted above the dimmed surround

Face check

Set shape="oval" and pass progress from 0 to 1 to draw an arc around the oval. Set status="found" when the check is done.

Selfie check

Turn your head slowly

Keep your face inside the oval

"use client"

import * as React from "react"

QR code

Set shape="square" and scanning to show the scan line. The line stops when status is "found".

Scan to pay

Point the camera at a QR code

Payment links and bill codes work

"use client"

import * as React from "react"

Error

Set status="error" to tint the corners, and say what went wrong in the hint. Use ratio to match the document, such as 1.42 for a passport spread.

Passport

Too much glare

Turn off the flashlight or tilt the passport away from the light

import {
  CameraFrame,
  CameraFrameActions,

Accessibility

CameraFrame renders a group. The shutter, actions and torch are React Aria buttons, so they work with a keyboard, a pointer and touch.

Name the frame

Pass aria-label to CameraFrame so the group is announced, such as "ID photo" or "QR scanner".

Say what to do in the hint

The viewfinder is decorative and hidden from assistive tech. CameraFrameHint has role="status", so its text is read politely whenever it changes. Keep it short and change it only when the instruction really changes.

Label the buttons

CameraFrameShutter is named "Take photo" and CameraFrameTorch is named "Flashlight" unless you pass aria-label. CameraFrameAction shows only an icon, so aria-label is required. The torch is a toggle button, so its on or off state is announced.

<CameraFrameAction aria-label="Choose from gallery">
  <ImageIcon />
</CameraFrameAction>

Name the progress

On an oval frame, progress renders a progress bar named by progressLabel, "Check progress" by default.

API Reference

CameraFrame

The root. Accepts all <div> props.

PropTypeDefaultDescription
variant"screen" | "inset""screen"screen fills its container. inset is a rounded, fixed-height panel.
classNamestring-Additional classes to apply to the root.

CameraFrameMedia

Holds the camera picture, a <video> or <img>, behind every other part. Accepts all <div> props.

PropTypeDefaultDescription
childrenReact.ReactNode-Your <video> or <img>.
classNamestring-Additional classes to apply to the media slot.

CameraFrameHeader

The top row: close, title and help. Items after the title are aligned to the end. Accepts all <div> props.

PropTypeDefaultDescription
classNamestring-Additional classes to apply to the header.

CameraFrameTitle

The screen title, centered in the header. Renders an <h2> and accepts all of its props.

PropTypeDefaultDescription
classNamestring-Additional classes to apply to the title.

CameraFrameViewfinder

The cut-out the user lines the subject up with. Accepts all <div> props.

PropTypeDefaultDescription
shape"document" | "oval" | "square""document"document for cards and passports, oval for a face, square for QR codes.
rationumber1.586Width over height of the document frame, e.g. 1.42 for a passport spread.
status"searching" | "found" | "error""searching"found settles the frame, error tints the corners.
scanningbooleanfalseShows the scan line. Stops when status is "found".
progressnumber-Progress of a face check, 0 to 1. Draws an arc on the oval shape only.
progressLabelstring"Check progress"Names the progress arc for assistive tech.
childrenReact.ReactNode-Decorative guides drawn inside the frame. Hidden from assistive tech.
classNamestring-Additional classes to apply to the viewfinder.

CameraFrameHint

What to do now. Has role="status". Accepts all <div> props.

PropTypeDefaultDescription
classNamestring-Additional classes to apply to the hint.

CameraFrameHintTitle

The main instruction. Renders a <p> and accepts all of its props.

PropTypeDefaultDescription
classNamestring-Additional classes to apply to the title.

CameraFrameHintDescription

Supporting text under the instruction. Renders a <p> and accepts all of its props.

PropTypeDefaultDescription
classNamestring-Additional classes to apply to the description.

CameraFrameActions

The bottom row: secondary actions either side of the shutter. Accepts all <div> props.

PropTypeDefaultDescription
classNamestring-Additional classes to apply to the row.

CameraFrameShutter

The round capture button. Accepts React Aria Button props except children.

PropTypeDefaultDescription
onPress(e: PressEvent) => void-Called when the shutter is pressed.
aria-labelstring"Take photo"Name of the button.
isDisabledboolean-Disables the shutter, e.g. while processing.
classNamestring-Additional classes.

CameraFrameAction

A round icon action, such as picking from the gallery. Accepts React Aria Button props.

PropTypeDefaultDescription
aria-labelstring-Name of the button. Required.
childrenReact.ReactNode-The icon.
classNamestring-Additional classes.

CameraFrameTorch

The flashlight toggle. Renders its own icon. Accepts React Aria ToggleButton props except children.

PropTypeDefaultDescription
isSelectedboolean-Whether the flashlight is on (controlled).
defaultSelectedboolean-Whether the flashlight is on (uncontrolled).
onChange(isSelected: boolean) => void-Called when the flashlight is toggled.
aria-labelstring"Flashlight"Name of the button.
classNamestring-Additional classes.