react-face-liveness
v1.0.0
Published
A React component for real-time face liveness detection — checks face visibility, open eyes, straight gaze, and no occlusion before allowing a selfie capture.
Maintainers
Readme
react-face-liveness
A production-ready React component for real-time face liveness detection using MediaPipe Face Mesh.
Before allowing a selfie capture, the component verifies all of the following:
| Check | What it detects | |---|---| | 👤 Face detected | A face is present in the frame | | 🫥 Face not covered | No hand, hat, mask, or object blocking the face | | 👁️ Eyes open | Both eyes are fully open | | 🎯 Looking straight | No head tilt or horizontal turn beyond threshold | | 📐 Face centered | Face is large enough and centered in frame |
Installation
npm install react-face-liveness
# or
yarn add react-face-liveness
# or
pnpm add react-face-livenessNo additional dependencies needed. MediaPipe Face Mesh is loaded from CDN at runtime so your bundle stays small.
Quick Start
React (Vite / CRA / etc.)
import { FaceLiveness } from 'react-face-liveness';
export default function App() {
const handleCapture = (imageDataUrl: string) => {
console.log('Selfie captured!', imageDataUrl);
// e.g. upload to your server
};
return (
<FaceLiveness
onCapture={handleCapture}
width={480}
height={360}
/>
);
}Next.js App Router (recommended)
Because FaceLiveness uses browser APIs (camera, window, requestAnimationFrame), it must run on the client. Use FaceLivenessLazy in a Server Component or add "use client" to your file:
Option A — FaceLivenessLazy (zero config, works anywhere)
// app/kyc/page.tsx ← Server Component, no "use client" needed
import { FaceLivenessLazy } from 'react-face-liveness';
export default function KYCPage() {
return (
<main>
<h1>Identity Verification</h1>
<FaceLivenessLazy
onCapture={(img) => console.log('captured', img)}
width={480}
height={360}
/>
</main>
);
}Option B — FaceLiveness with "use client"
// app/kyc/CameraWidget.tsx
'use client';
import { FaceLiveness } from 'react-face-liveness';
export function CameraWidget({ onCapture }: { onCapture: (img: string) => void }) {
return <FaceLiveness onCapture={onCapture} />;
}// app/kyc/page.tsx ← Server Component
import { CameraWidget } from './CameraWidget';
export default function KYCPage() {
return <CameraWidget onCapture={(img) => console.log(img)} />;
}Next.js Pages Router
// pages/verify.tsx
import dynamic from 'next/dynamic';
const FaceLiveness = dynamic(
() => import('react-face-liveness').then((m) => m.FaceLiveness),
{ ssr: false }
);
export default function VerifyPage() {
return (
<FaceLiveness
onCapture={(img) => console.log('done', img)}
width={480}
height={360}
/>
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
| onCapture | (dataUrl: string) => void | required | Called with base64 PNG when selfie is taken |
| onStatusChange | (status: LivenessStatus) => void | undefined | Called every frame with liveness check results |
| width | number | 480 | Component width in px |
| height | number | 360 | Component height in px |
| className | string | undefined | CSS class on root div |
| style | CSSProperties | undefined | Inline styles on root div |
| captureButtonLabel | string | "Take Selfie" | Button label |
| showStatusOverlay | boolean | true | Show built-in check badges and message |
| showCaptureButton | boolean | true | Show built-in capture button |
| eyeOpenThreshold | number | 0.2 | EAR threshold — lower = more lenient |
| maxTiltDegrees | number | 15 | Max head roll (tilt) in degrees |
| maxYawDegrees | number | 20 | Max head yaw (turn) in degrees |
| minFaceAreaFraction | number | 0.15 | Min fraction of frame the face must occupy |
LivenessStatus object
Passed to onStatusChange every frame:
interface LivenessStatus {
faceDetected: boolean;
faceNotCovered: boolean;
eyesOpen: boolean;
faceStraight: boolean;
faceInFrame: boolean;
allChecksPassed: boolean; // true when all 5 checks pass
message: string; // human-readable guidance
}Advanced — use the hook directly
Build your own UI while reusing all the detection logic:
'use client';
import { useFaceLiveness } from 'react-face-liveness';
export function CustomCamera() {
const { videoRef, canvasRef, status, isLoading, error, capture } =
useFaceLiveness({
eyeOpenThreshold: 0.18,
maxTiltDegrees: 12,
maxYawDegrees: 18,
});
const handleCapture = () => {
if (!status.allChecksPassed) return;
const dataUrl = capture();
console.log(dataUrl);
};
return (
<div style={{ position: 'relative', width: 480, height: 360 }}>
<video ref={videoRef} muted playsInline style={{ width: '100%' }} />
<canvas ref={canvasRef} style={{ position: 'absolute', inset: 0 }} />
{isLoading && <p>Loading…</p>}
{error && <p style={{ color: 'red' }}>{error}</p>}
<p>{status.message}</p>
<button onClick={handleCapture} disabled={!status.allChecksPassed}>
Capture
</button>
</div>
);
}Local development & testing
The package includes a fully-featured demo app in the demo/ folder so you can test everything locally before publishing — no second project needed.
Run the demo in one command
# From the package root
npm run demoThis installs demo dependencies and starts Vite at http://localhost:5173. The demo automatically points react-face-liveness at your local src/ folder via a Vite alias, so every change you make to src/ hot-reloads instantly — no build step required.
What the demo includes
| Tab | What it tests |
|---|---|
| <FaceLiveness /> | The ready-made component with built-in UI, onCapture, onStatusChange, and a live event log |
| useFaceLiveness() | The raw hook wired up to a custom UI — shows the full LivenessStatus object updating live |
| Props playground | Sliders to tune every threshold (eyeOpenThreshold, maxTiltDegrees, etc.) and see the effect in real-time |
Publishing to npm
# 1. Build the distributable
npm run build
# 2. Check what will be published (demo/ is excluded via .npmignore)
npm pack --dry-run
# 3. Publish
npm publishHow it works
- Camera stream — opened via
getUserMediain auseEffect(client-only). - MediaPipe Face Mesh — 468 3-D facial landmarks at ~30 fps, loaded from CDN.
- Eye open check — Eye Aspect Ratio (EAR) computed from landmark distances.
- Head pose — Roll (tilt) and yaw (turn) derived from key landmark geometry.
- Occlusion check — Z-depth variance across the face oval; abnormal variance indicates a hand or hat.
- Face size — Bounding box area relative to frame size.
- Selfie capture — When all checks pass, the button activates. Clicking renders the current video frame to a hidden
<canvas>and returns a PNG data URL.
Browser support
| Browser | Support | |---|---| | Chrome 88+ | ✅ Full | | Firefox 90+ | ✅ Full | | Safari 15.4+ | ✅ Full | | Edge 88+ | ✅ Full |
HTTPS is required for camera access in production.
License
MIT
