npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

modern-barcode-scanner

v2.0.0

Published

A high-performance React barcode scanner with an owned WebAssembly engine and capability-aware camera controls

Readme

GitHub Actions Workflow Status NPM Version NPM Downloads Socket Badge License: MIT


Interface Preview

Modern Barcode Scanner in its scanner-ready idle state

Desktop idle state at 1440 × 900. The same interface is audited across desktop, tablet, and mobile in the design audit.


✨ Features

  • 🚀 High Performance: A first-party ZXing-C++ WebAssembly engine prioritizes the visible scan region, scores frame quality off-thread, coalesces stale queued work, reuses memory, and copies only luminance into WASM.
  • 📱 Mobile Optimized: Responsive camera constraints without mandatory zoom or focus settings.
  • 🔦 Torch Control: Shown only when the active camera reports torch support.
  • 🔄 Camera Switching: Shown only when multiple video inputs are available.
  • 🎯 Session Management: Prevents stale results with session-based tracking.
  • 🎨 Customizable UI: CSS-based styling with sensible defaults and CSS variables.
  • Accessible Controls: Keyboard focus states, reduced-motion support, live scanner status, and mobile-safe touch targets.
  • 📦 TypeScript Support: Full type definitions included out of the box.
  • 📳 Haptic Feedback: Standard Web Vibration API support for successful scans where navigator.vibrate is available.
  • 🔊 Sound Feedback: Optional audio cues on successful scans.

🏷️ Supported Barcode Formats

  • 2D Codes: QR Code, Micro QR, rMQR, Data Matrix, PDF417/MicroPDF417, Aztec, MaxiCode
  • Retail Codes: EAN-13, EAN-8, UPC-A, UPC-E, including valid 2- and 5-digit retail supplements
  • Industrial/Standard Codes: Code 128, Code 39, Code 93, Codabar, ITF (Interleaved 2 of 5)
  • Books: Bookland ISBN-13
  • DataBar (GS1)
  • Additional: Telepen, Code 32, DX Film Edge, and more supported by the pinned ZXing-C++ reader build

📦 Installation

Choose your preferred package manager:

# npm
npm install modern-barcode-scanner

# yarn
yarn add modern-barcode-scanner

# pnpm
pnpm add modern-barcode-scanner

🚀 Quick Start

Here's a minimal example to get the scanner up and running in your React application:

import { useRef, useEffect } from "react";
import { BarcodeScanner } from "modern-barcode-scanner";
import type { 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>
  );
}

Zero bundler configuration

Under the hood, this library uses its own pinned, reader-only ZXing-C++ WebAssembly build behind a modular decoder boundary. Both the worker and its WebAssembly binary are inlined directly into the bundle — the worker as a Blob and the .wasm inside a single-file Emscripten module.

This means the published package does not require a separately hosted worker or .wasm asset: install it, import it, and include its stylesheet. The production package path is exercised end to end in the browser test matrix. Applications with unusual bundle transforms or a restrictive Content Security Policy should retain the package's Blob worker and run the browser tests described below.

The trade-off is a larger main bundle (the WASM binary is embedded), in exchange for it working out of the box in supported consumers with no hosted engine assets.

The source lock, reproducible build, artifact checksums, SBOM, test matrix, and smart-enhancement roadmap are documented in the first-party decoder migration. Responsive states, visual decisions, and the current Hallmark review are recorded in the design audit. No @undecaf/zbar-wasm runtime dependency remains.


📖 API Reference

<BarcodeScanner /> Component

Props

| 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 color for the viewfinder, scan line, and controls. | | 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. |

Ref Methods

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
}

TypeScript Types

interface ScanResult {
  typeName: string; // e.g., 'QRCODE', 'EAN13', 'CODE128'
  scanData: string; // The decoded barcode string
}

interface ScannerState {
  isStarting: boolean;
  isScanning: boolean;
  facingMode: "user" | "environment";
  isTorchOn: boolean;
  isTorchSupported: boolean;
  canSwitchCamera: boolean;
}

🛠️ Advanced Usage

Using the useScanner Hook

If you need complete control over the UI, you can use the exported hook directly:

import {
  IconCamera,
  IconCameraOff,
  IconRotateCamera,
  IconTorchOff,
  IconTorchOn,
  useScanner,
} from "modern-barcode-scanner";

function CustomScanner() {
  const {
    scannerState,
    videoRef,
    canvasRef,
    viewfinderRef,
    handleScan,
    handleStopScan,
    handleSwitchCamera,
    handleToggleTorch,
  } = useScanner({
    onScan: (result) => console.log("Scanned:", result),
    onError: (error) => console.error("Error:", error),
    enableVibration: true,
  });

  return (
    <div style={{ position: "relative", width: "100%", height: 480 }}>
      <video
        ref={videoRef}
        autoPlay
        muted
        playsInline
        style={{ width: "100%", height: "100%", objectFit: "cover" }}
      />
      <div
        ref={viewfinderRef}
        style={{ position: "absolute", inset: "25% 10%", border: "2px solid #2563eb" }}
      >
        Align the barcode here
      </div>
      <canvas ref={canvasRef} hidden />

      <div className="controls">
        <button type="button" onClick={handleScan}>
          <IconCamera /> Start
        </button>
        <button type="button" onClick={handleStopScan}>
          <IconCameraOff /> Stop
        </button>
        {scannerState.canSwitchCamera && (
          <button type="button" onClick={handleSwitchCamera}>
            <IconRotateCamera /> Switch camera
          </button>
        )}
        {scannerState.isTorchSupported && (
          <button type="button" onClick={handleToggleTorch}>
            {scannerState.isTorchOn ? <IconTorchOff /> : <IconTorchOn />}
            {scannerState.isTorchOn ? "Turn off torch" : "Turn on torch"}
          </button>
        )}
      </div>
    </div>
  );
}

Attach viewfinderRef to the region represented by your custom guide. The hook maps that displayed rectangle through the camera video's object-fit: cover crop and prioritizes it for decoding; when no measurable guide is attached, it safely falls back to full-frame scanning.

For custom interfaces, the package also exports ScannerControls, ScanLine, and the complete 24 px icon system: IconCamera, IconCameraOff, IconCameraPlaceholder, IconRotateCamera, IconTorchOn, IconTorchOff, IconScanFrame, IconCheck, IconAlert, and IconAdjustments. IconCameraPlaceholder remains a backward-compatible alias of the canonical IconScanFrame logo. Every icon inherits currentColor and accepts standard React SVG props.

Helper Utilities

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 a heuristically preferred rear camera device ID
const cameraId = await getBestRearCamera();

// ⚙️ Get optimized media constraints based on facing mode
const constraints = await getMediaConstraints("environment");

getBestRearCamera() requests camera permission and probes available video inputs to identify a heuristically preferred rear camera. Camera labels and capabilities vary by browser and device, and the helper can open each camera briefly, so it remains an explicit advanced utility rather than part of the default startup path. getMediaConstraints() uses non-mandatory facing and resolution preferences for faster, more resilient startup.


🎨 Styling

The component uses CSS prefix mbs- (Modern Barcode Scanner) and component-scoped CSS variables for easy theming. It fills the dimensions of its parent, so give the parent an explicit height when embedding it in a page.

<div style={{ width: "100%", height: 480 }}>
  <BarcodeScanner onScan={handleScan} />
</div>

The scanner establishes its own CSS size container, so the viewfinder responds to the component's actual host rather than the browser viewport. This keeps embedded 250–480 px scanners balanced inside wide desktop layouts while retaining viewport-media fallbacks for older browsers.

CSS Variables

Override tokens on a scanner instance (or use the themeColor prop for the primary accent):

.my-scanner {
  --mbs-primary: #ff0055;
  --mbs-scan-color: #ff0055;
  --mbs-bg: #fff8fb;
  --mbs-bg-secondary: #ffeef5;
  --mbs-control-bg: rgba(20, 10, 16, 0.68);
}

Choose accent and background colors with sufficient contrast for your application, especially when overriding the defaults. Independent dark and light cue rails keep the viewfinder brackets and scan beam identifiable over both bright phone displays and dark camera feeds, including black or white custom accents.

| Token | Default | Purpose | | ---------------------- | ----------------------------- | ------------------------------------------ | | --mbs-primary | oklch(53% 0.21 256) | Viewfinder brackets and primary accent | | --mbs-scan-color | var(--mbs-primary) | Animated scan line and restrained trail | | --mbs-bg | oklch(98.5% 0.004 250) | Scanner background | | --mbs-bg-secondary | oklch(95.5% 0.012 250) | Background highlight | | --mbs-bg-card | oklch(99% 0.004 250 / 0.95) | Raised scanner surface | | --mbs-text | oklch(24% 0.02 258) | Primary text | | --mbs-text-secondary | oklch(45% 0.018 257) | Secondary text | | --mbs-control-bg | oklch(18% 0.018 258 / 0.82) | Camera-control toolbar background | | --mbs-control-opaque | oklch(18% 0.018 258) | Opaque camera hint on constrained layouts | | --mbs-control-hover | oklch(95% 0.008 250 / 0.18) | Camera-control hover background | | --mbs-border | oklch(53% 0.21 256 / 0.2) | Subtle borders and placeholder glow | | --mbs-on-dark | oklch(96% 0.008 250) | Text and icons on camera surfaces | | --mbs-scrim | oklch(12% 0.014 258 / 0.18) | Area outside the prioritized viewfinder | | --mbs-cue-dark | oklch(8% 0.01 258 / 0.82) | Dark safety rail around the scanning cue | | --mbs-cue-light | oklch(98% 0.004 250 / 0.9) | Light safety rail around the scanning cue | | --mbs-radius-control | 0.375rem | Button and hint corner radius | | --mbs-radius-frame | 0.875rem | Viewfinder and control-group corner radius |

Overriding Classes

/* 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 Requirements

The scanner targets current Chrome, Edge, Firefox, and Safari releases on desktop and mobile. A browser must provide:

  • navigator.mediaDevices.getUserMedia for camera access.
  • Web Workers, WebAssembly, Canvas, and requestAnimationFrame.
  • A secure context. Use HTTPS in production (localhost is allowed for local development).

If your application enforces Content Security Policy, allow the package's self-contained Blob worker (normally worker-src 'self' blob:). Depending on browser and policy version, WebAssembly compilation may also require script-src 'wasm-unsafe-eval' or the broader legacy fallback 'unsafe-eval'; start with the narrower directive and verify your supported browsers. The scanner does not use JavaScript eval, external worker files, or cross-origin .wasm fetches. When embedded in an iframe, grant camera access with allow="camera" and an appropriate Permissions-Policy response header.

Camera switching, torch, vibration, and audio feedback depend on device and browser support. Unsupported optional features degrade gracefully; use onError to surface permission, camera, ended-stream, worker bootstrap/runtime/timeout, and torch failures to users. Camera dimensions are re-synchronized after metadata, orientation, resolution, and camera changes instead of relying on a startup snapshot.

The scanner processes frames locally in the browser and does not upload camera data.

The automated browser matrix exercises the Chromium, Firefox, and WebKit engines plus a Chromium fake-camera pipeline. Real mobile camera selection, torch behavior, autofocus, thermal limits, and permission UI still depend on physical hardware and should be included in an application's device acceptance testing.


⚡ Performance Optimizations

This library is built for speed and reliability:

  1. Web Worker Processing: Barcode detection runs entirely off the main thread.
  2. Adaptive Frame Quality: Uses a bounded, anti-aliasing luminance sample to score motion, blur, contrast, exposure, and glare before spending work on WASM decoding. Every third focused attempt remains an enhanced recovery pass, preventing a valid code on a bright phone screen from being filtered indefinitely.
  3. Viewfinder-First Detection: Scans the smaller guided region first and restores a complete, deeper full-frame pass every fifth attempt.
  4. Reusable WASM Frame Memory: Grows the decoder input allocation only when needed and reuses it across scans.
  5. Frame Throttling: Configurable scanInterval balances detection latency with device battery and CPU usage.
  6. Session Management: Monotonic sessions and exact request IDs prevent late or duplicate worker responses from unlocking a newer frame.
  7. Smart Downscaling: Bounds the captured frame to 1280 px on its longest side while preserving its aspect ratio, reducing work without distorting the symbol.
  8. Canvas Optimizations: Utilizes willReadFrequently and desynchronized rendering hints where supported.
  9. Failure Recovery: Readiness deadlines, bounded capture failures, worker response watchdogs, and camera-track lifecycle handling prevent silent scanning stalls.
  10. Defensive WASM Boundary: Both TypeScript and native code validate dimensions, byte lengths, heap ranges, engine results, and terminal decoder lifecycle state.
  11. Bounded Worker Backpressure: The worker accepts at most 1280 px per dimension and 1280² pixels, keeps at most one queued frame per scanner, and releases a superseded transferred buffer before decoding.

🧑‍💻 Development

This project uses Vite+ (vp) as its unified toolchain for Vite, Vitest, Oxlint, Oxfmt, Rolldown, tsdown, and Vite Task workflows. Development requires Node.js ^22.22.2, ^24.15.0, or >=26.0.0 and npm >=11.5.1. The npm scripts invoke the locally installed vp binary.

Dependency freshness was rechecked on 2026-08-23: the locked direct dependency set, Playwright browser bundle, ZXing-C++ 3.1.1 engine, and Emscripten 6.0.8 toolchain were current, npm outdated was empty, and the dependency audit reported no known vulnerabilities. React 18 and 19 remain the supported peer ranges even though development and verification use the current React 19 line.

# Install the exact locked dependency graph (includes Vite+)
npm ci

# Start the live demo app at http://localhost:8080
npm run dev

Scripts

| 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. | | npm run benchmark:engine | Run the repeatable warm focused/enhanced 392 × 392 QR engine benchmark. | | npm run test:browser | Build, then test visual layouts, the worker, and a fake camera across browsers. | | npm run engine:build | Rebuild the owned WASM engine from pinned source and toolchain inputs. | | npm run engine:verify | Cross-check the native ABI, source/toolchain lock, SBOM, declaration, and artifact SHA-256. | | npm run fixtures:browser | Regenerate deterministic QR fixtures used by the browser and fake-camera tests. |

Demo

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.

Append ?visual-audit to the local demo URL to open the camera-free core component audit page. It renders the real package CSS, the complete icon system, viewfinder variants, exported controls, capability-limited controls, and custom accents without requesting camera permission.

For deterministic responsive QA of the complete demo, use ?demo-state=<state>, where <state> is idle, starting, active, result, or error. These previews reuse the production components and styles without requiring camera hardware or a test barcode. The maintained viewport/state matrix and current findings are in docs/DESIGN_AUDIT.md.

Testing

Unit and integration 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 component tests and independently generated real barcode fixtures for decoder tests. They include native ABI boundaries, malformed and detached buffers, worker backpressure and request coalescing, frame-quality/ROI behavior, retail supplements, camera lifecycle races, and engine recovery after damaged input. Playwright tests under tests/browser validate responsive desktop, portrait, compact, and short-landscape layouts; 250–480 px embedded hosts; 125–200% text sizing; rendered pixels in all eight corner arms; black/white accent extremes; hostile error strings; ultra-short result surfaces; dark, forced-colors, and reduced-motion preferences; keyboard focus containment; the production-style inline worker and transferable frame buffer in Chromium, Firefox, and WebKit; and the built public package through the complete Chromium fake-camera pipeline. Pull requests and pushes to dev or main repeat the static, dependency, fixed-platform reproducible-engine, unit, browser, build, and package gates in CI. Run the primary local suites with npm test and npm run test:browser, and track warm decoder performance with npm run benchmark:engine.


📝 License

MIT © Sumit Sahoo

Please refer to the LICENSE file for the project license and THIRD_PARTY_NOTICES.md for bundled decoder and generated-runtime notices.


🤝 Credits

  • Barcode decoding powered by this repository's pinned, reader-only ZXing-C++ WebAssembly build.
  • WebAssembly runtime generated with the pinned Emscripten 6.0.8 toolchain.