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

otp-pro-input

v0.1.3

Published

Headless, fully customizable OTP / segmented code input for React

Readme

otp-pro-input

npm version License: MIT Bundle size

Headless, fully customizable OTP / segmented code input for React. Ship your own UI or use the optional default theme — paste, autofill, keyboard nav, and accessibility built in.

Live demo →


Table of contents


Features

  • Headless-firstuseOtpInput hook with all logic, zero forced styling
  • Pre-built component<OtpInput /> works out of the box with optional CSS
  • Paste handling — full/partial codes, sanitizes dashes, spaces, invalid chars
  • Keyboard navigation — arrows, Home/End, Backspace/Delete edge cases
  • iOS SMS autofillautocomplete="one-time-code"
  • WebOTP API — Android Chrome SMS autofill (feature-detected)
  • Masked PIN mode, error/disabled/loading states
  • Accessible — real <input> elements, ARIA labels, axe-clean default
  • React 18 & 19 — StrictMode-safe, concurrent rendering compatible
  • Zero runtime dependencies — tree-shakeable, ~3 KB gzipped (core)

Install

npm install otp-pro-input
yarn add otp-pro-input
pnpm add otp-pro-input

Peer dependencies: react and react-dom (≥18)


Quick start

import { OtpInput } from 'otp-pro-input';
import 'otp-pro-input/styles.css';

export function VerifyPage() {
  return (
    <OtpInput
      length={6}
      onComplete={(code) => console.log('Submitted:', code)}
    />
  );
}

Usage

Default component

<OtpInput
  length={6}
  groups={[3, 3]}
  renderSeparator={() => '–'}
  onChange={(code) => console.log(code)}
  onComplete={(code) => verify(code)}
/>

Headless hook

Full control over markup and styling:

import { useOtpInput } from 'otp-pro-input';

function MyOtp() {
  const { slots, clear, focus } = useOtpInput({
    length: 6,
    onComplete: (code) => verify(code),
  });

  return (
    <div role="group" aria-label="Verification code, 6 digits">
      {slots.map((slot) => (
        <input
          key={slot.index}
          {...slot}
          ref={slot.inputRef}
          value={slot.value}
          className="my-slot"
        />
      ))}
      <button type="button" onClick={clear}>Clear</button>
    </div>
  );
}

Custom slots (renderInput)

<OtpInput
  length={6}
  useDefaultStyles={false}
  groups={[3, 3]}
  renderSeparator={() => '–'}
  renderInput={(props) => {
    const { inputRef, displayValue, ...inputProps } = props;
    return (
      <input
        {...inputProps}
        ref={inputRef}
        value={displayValue}
        className={props['aria-invalid'] ? 'slot slot--error' : 'slot'}
      />
    );
  }}
  onComplete={handleVerify}
/>

Imperative ref

import { useRef } from 'react';
import { OtpInput, type OtpInputHandle } from 'otp-pro-input';

const ref = useRef<OtpInputHandle>(null);

<OtpInput ref={ref} length={6} />

ref.current?.focus(0);
ref.current?.clear();
ref.current?.getValue();
ref.current?.setValue('123456');

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | length | number | required | Number of OTP slots (3–8; values outside this range are clamped) | | value | string | — | Controlled value | | defaultValue | string | '' | Uncontrolled initial value | | onChange | (code: string) => void | — | Fired on every change | | onComplete | (code: string) => void | — | Fired once when all slots are filled | | allowedChars | 'numeric' \| 'alphanumeric' \| RegExp | 'numeric' | Valid characters per slot | | autoFocus | boolean | false | Focus first empty slot on mount | | disabled | boolean | false | Disable all slots | | readOnly | boolean | false | Read-only slots | | error | boolean \| string | — | Sets aria-invalid and data-error | | mask | boolean \| string | — | Mask display character (default ) | | loading | boolean | false | Disables input while verifying | | renderLoading | () => ReactNode | — | Custom loading indicator | | renderInput | (props) => ReactElement | — | Custom slot renderer | | renderSeparator | (index) => ReactNode | — | Separator between slots (shown after every input except the last) | | groups | number[] | — | Visual grouping, e.g. [3, 3] | | groupLabel | string | 'One-time passcode' | Accessible group name | | getSlotLabel | (index, length) => string | 'Digit N of M' | Per-slot aria-label | | dir | 'ltr' \| 'rtl' | 'ltr' | Text direction | | enableWebOtp | boolean | false | Enable WebOTP API (Android) | | enableAutofill | boolean | true | iOS SMS autofill support | | announceComplete | boolean \| string | false | aria-live completion announcement | | useDefaultStyles | boolean | !renderInput | Apply default BEM class names | | className | string | — | Root container class | | ref | OtpInputHandle | — | Imperative API |


Styling

Import the optional default stylesheet (never auto-injected):

import 'otp-pro-input/styles.css';

Override via CSS custom properties:

.my-otp {
  --otp-border-color: #6366f1;
  --otp-border-color-focus: #4f46e5;
  --otp-slot-size: 3rem;
  --otp-border-radius: 0.75rem;
  --otp-focus-ring: 0 0 0 3px rgba(99, 102, 241, 0.25);
}

Available variables include --otp-gap, --otp-group-gap, --otp-font-size, --otp-background, --otp-text-color, --otp-error-color, and more.


Accessibility

  • Each slot is a native <input> (not a styled <div>)
  • Group uses role="group" with a descriptive aria-label
  • Per-slot labels, e.g. "Digit 1 of 6" (customizable)
  • aria-invalid and data-error for error states
  • Optional aria-live="polite" completion announcement
  • Visible focus indicator in the default theme
  • 44×44px minimum touch targets in the default theme
  • Natural tab order — no focus trap

Tested with axe (zero violations on the default component).


Autofill

iOS Safari

Sets autocomplete="one-time-code" and uses a hidden capture input. When iOS suggests a code from Messages, all slots fill and onComplete fires.

Android Chrome (WebOTP)

<OtpInput length={6} enableWebOtp onComplete={verify} />

Uses the WebOTP API. Feature-detected — no-ops gracefully on unsupported browsers.


Browser support

| Browser | Support | |---------|---------| | Chrome (last 2) | ✅ | | Firefox (last 2) | ✅ | | Safari (last 2) | ✅ | | Edge (last 2) | ✅ | | iOS Safari | ✅ SMS autofill | | Android Chrome | ✅ WebOTP + paste |


Links


Legal

Your rights

otp-pro-input is released under the MIT License. You may use, modify, and distribute it freely in personal and commercial projects, provided you include the copyright notice.

What npm users receive

The published package contains no runtime dependencies. Only dist/ (your compiled library + optional CSS) is shipped. React is a peer dependency — consumers bring their own copy.

See THIRD_PARTY_NOTICES.md on GitHub for additional notices.

Original code

All library source is original work for this project. It does not bundle code from other OTP libraries.

Trademarks

“React” is a trademark of Meta Platforms, Inc. This project is an independent open-source library and is not affiliated with or endorsed by Meta.

Disclaimer

This software is provided “as is” without warranty. It is a client-side UI component only — it does not verify, store, or transmit OTP codes to any server. You are responsible for secure verification on your backend and for compliance with laws applicable to your app (e.g. privacy, accessibility).

This is not legal advice. For specific compliance questions, consult a qualified attorney.


License

MIT © 2026 Pranay Surve