A high-performance barcode scanner React component with optimized detection, camera switching, torch control, and automatic phone detection.
- π High Performance: Web Worker-based scanning with optimized grayscale conversion.
- π± Mobile Optimized: Automatic phone detection with appropriate camera selection.
- π¦ Torch Control: Built-in torch/flash support for low-light scanning.
- π Camera Switching: Easy switching between front and back cameras.
- π· Smart Camera Selection: Automatically selects the best rear camera (avoids ultra-wide/telephoto).
- π― Session Management: Prevents stale results with session-based tracking.
- π¨ Customizable UI: CSS-based styling with sensible defaults and CSS variables.
- π¦ TypeScript Support: Full type definitions included out of the box.
- π³ Haptic Feedback: Standard Web Vibration API support for successful scans (Android/Desktop).
- π Sound Feedback: Optional audio cues on successful scans.
- 2D Codes: QR Code, PDF417
- Retail Codes: EAN-13, EAN-8, UPC-A, UPC-E
- Industrial/Standard Codes: Code 128, Code 39, Code 93, Codabar, ITF (Interleaved 2 of 5)
- Books: ISBN-10, ISBN-13
- DataBar (GS1)
- And more! (Powered by ZBar)
Choose your preferred package manager:
# npm
npm install modern-barcode-scanner
# yarn
yarn add modern-barcode-scanner
# pnpm
pnpm add modern-barcode-scannerHere's a minimal example to get the scanner up and running in your React application:
import { useRef, useEffect } from "react";
import { BarcodeScanner, BarcodeScannerRef, ScanResult } from "modern-barcode-scanner";
// Import the stylesheet once, anywhere in your app. The CSS ships as a separate
// file (so you can override the design tokens), so it is NOT injected
// automatically β this import is required for the scanner to look right.
import "modern-barcode-scanner/styles.css";
function App() {
const scannerRef = useRef<BarcodeScannerRef>(null);
const handleScan = (result: ScanResult) => {
console.log("π¦ Barcode type:", result.typeName);
console.log("π Barcode data:", result.scanData);
// Scanner automatically stops after detection.
// Call scannerRef.current?.start() to scan again!
};
const handleError = (error: Error) => {
console.error("β Scanner error:", error.message);
};
useEffect(() => {
// Start scanning when component mounts
scannerRef.current?.start();
}, []);
return (
<div style={{ width: "100vw", height: "100vh" }}>
<BarcodeScanner
ref={scannerRef}
onScan={handleScan}
onError={handleError}
themeColor="#2563EB" // Customize the primary UI color!
/>
</div>
);
}Under the hood, this library uses @undecaf/zbar-wasm for detection, running inside a Web Worker. Both the worker and its WebAssembly binary are inlined directly into the bundle β the worker as a Blob and the .wasm as embedded data.
This means you do not need any special bundler setup: no optimizeDeps exclusions, no copying a worker file out of node_modules, and no rules to serve .wasm assets. Just install, import, and go β it works the same across Vite, webpack, Next.js, and other bundlers.
The trade-off is a larger main bundle (the WASM binary is embedded), in exchange for it working out of the box in any consumer with no setup.
| Prop | Type | Default | Description |
|---|---|---|---|
onScan |
(result: ScanResult) => void |
Required | Callback fired when a barcode is detected. |
onError |
(error: Error) => void |
undefined |
Callback fired when an error occurs. |
onStateChange |
(state: ScannerState) => void |
undefined |
Callback fired when scanner state changes. |
themeColor |
string |
'#2563EB' |
Primary theme color for UI elements and scan line. |
scanInterval |
number |
100 |
Time between scan attempts (in ms). |
enableVibration |
boolean |
true |
Enable haptic feedback on scan (uses navigator.vibrate). |
vibrationDuration |
number |
200 |
Vibration duration (in ms). |
enableSound |
boolean |
false |
Enable sound feedback on scan. |
initialFacingMode |
'user' | 'environment' |
'environment' |
Initial camera to use. |
showScanLine |
boolean |
true |
Show scanning animation line. |
showCameraSwitch |
boolean |
true |
Show camera switch button. |
showTorchButton |
boolean |
true |
Show torch button (if supported). |
className |
string |
'' |
Custom CSS class for the container. |
style |
React.CSSProperties |
undefined |
Custom inline styles for the container. |
Exposed via useImperativeHandle for direct control:
interface BarcodeScannerRef {
start: () => Promise<void>; // Starts the camera and scanning
stop: () => void; // Stops the camera and scanning
switchCamera: () => Promise<void>; // Toggles between front and back camera
toggleTorch: () => Promise<void>; // Toggles the torch/flash (if supported)
getState: () => ScannerState; // Returns current state
}interface ScanResult {
typeName: string; // e.g., 'QRCODE', 'EAN13', 'CODE128'
scanData: string; // The decoded barcode string
}
interface ScannerState {
isScanning: boolean;
facingMode: "user" | "environment";
isTorchOn: boolean;
}If you need complete control over the UI, you can use the internal hook directly:
import { useScanner } from "modern-barcode-scanner";
function CustomScanner() {
const {
scannerState,
videoRef,
canvasRef,
handleScan,
handleStopScan,
handleSwitchCamera,
handleToggleTorch,
} = useScanner({
onScan: (result) => console.log("Scanned:", result),
onError: (error) => console.error("Error:", error),
enableVibration: true,
});
return (
<div>
<video ref={videoRef} autoPlay muted playsInline />
<canvas ref={canvasRef} hidden />
<div className="controls">
<button onClick={handleScan}>βΆοΈ Start</button>
<button onClick={handleStopScan}>βΉοΈ Stop</button>
<button onClick={handleSwitchCamera}>π Switch</button>
{scannerState.isTorchOn ? "π¦ On" : "π¦ Off"}
</div>
</div>
);
}The library exports several useful utilities:
import { isPhone, getBestRearCamera, getMediaConstraints } from "modern-barcode-scanner";
// π± Check if device is a phone/tablet
const isMobile = isPhone();
// π· Get the optimal rear camera device ID (avoids ultra-wide lenses)
const cameraId = await getBestRearCamera();
// βοΈ Get optimized media constraints based on facing mode
const constraints = await getMediaConstraints("environment");The component uses CSS prefix mbs- (Modern Barcode Scanner) and supports native CSS variables for easy theming.
You can easily override the primary color globally or via the themeColor prop:
:root {
--mbs-primary: #ff0055; /* Changes scan line and active icon colors */
}/* Custom container styling */
.mbs-container {
border-radius: 1rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
/* Custom scan line */
.mbs-scan-line {
height: 3px;
box-shadow: 0 0 15px 3px var(--mbs-primary);
}
/* Custom control buttons */
.mbs-control-btn {
background-color: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(4px);
}| Browser / OS | Version |
|---|---|
| π’ Google Chrome | 79+ |
| π΅ Microsoft Edge | 79+ |
| π Mozilla Firefox | 79+ |
| π§ Safari (macOS) | 14.1+ |
| π± Chrome for Android | Supported |
| π Safari on iOS | 14.5+ |
β οΈ Note: Camera access requires a secure context (HTTPS) in production environments!
This library is built for speed and reliability:
- Web Worker Processing: Barcode detection runs entirely off the main thread.
- Grayscale Conversion: Uses bitwise operations for incredibly fast image matrix processing.
- Frame Throttling: Configurable
scanIntervalperfectly balances detection speed with device battery/CPU usage. - Session Management: Strictly prevents processing out-of-date or stale video frames.
- Smart Downscaling: Intelligently reduces image resolution for faster processing while maintaining read quality.
- Canvas Optimizations: Utilizes
willReadFrequentlyanddesynchronizedrendering hints where supported.
This project uses Vite+ (vp) as its unified toolchain β one tool for building, testing, linting, and formatting (it bundles Vite, Vitest, Oxlint, and Oxfmt). The npm scripts invoke the locally-installed vp binary, so npm install is all you need to get going.
# Install dependencies (includes Vite+)
npm install
# Start the live demo app at http://localhost:8080
npm run dev| Script | Description |
|---|---|
npm run dev |
Run the demo app with hot-module reload. |
npm run build |
Build the library (ESM + CJS) and emit type declarations (tsc). |
npm run preview |
Preview a production build of the demo. |
npm test |
Run the test suite once (Vitest + jsdom + Testing Library). |
npm run lint |
Lint the code with Oxlint. |
npm run format |
Format the code with Oxfmt. |
npm run check |
Format check + lint + type-check in a single command. |
npm run typecheck |
Type-check the library and the demo with tsc. |
The demo/ app consumes the library the same way a published consumer does β importing it by package name (modern-barcode-scanner) and stylesheet (modern-barcode-scanner/styles.css) against its public API. Aliases in demo/vite.config.ts resolve those entry points to the local build during development, keeping the demo live-reloading while validating the real package surface.
Tests live next to the source as *.test.ts(x) and run under Vitest (via vp test) in a jsdom environment, with @testing-library/react for the component tests. Run the whole suite with npm test.
MIT Β© Sumit Sahoo
Please refer to the LICENSE file for the full text.
- Powerful barcode detection engine powered by ZBar WASM.