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

wts-otp

v20.0.1

Published

Zero-dependency, accessible OTP, PIN, and verification-code input for JavaScript, Angular, React, Vue, Svelte, and Web Components.

Readme

wts-otp

A zero-dependency, accessible OTP, PIN, and verification-code input for vanilla JavaScript, Angular, React, Vue, Svelte, and other browser frameworks. Use the core DOM API or the optional form-associated Web Component without adding a framework runtime dependency.

wts-otp provides the user-interface control for entering a code. It does not generate or verify TOTP, HOTP, SMS, or authentication codes.

Install

npm install wts-otp

Core API

<div id="verification-code"></div>
import { WtsOtp } from 'wts-otp';

const otp = new WtsOtp('#verification-code', {
  autoFill: true,
  clipboardEnabled: true,
  digits: 6,
  type: 'number',
  visibleAs: 'text',
  onChange: (value) => console.log('Changed:', value),
  onComplete: (value) => console.log('Complete:', value),
});

otp.value = '123';
otp.focus(3);
otp.reset();

// Call when the owning screen or component is removed.
otp.destroy();

WtsOtp accepts a selector, an Element, or a ShadowRoot. The package does not access the browser DOM until an instance is constructed, so importing it during server-side rendering is safe.

Web Component

The Web Component is optional and uses a separate, explicit entry point:

import 'wts-otp/element';
<wts-otp
  digits="6"
  auto-fill="false"
  clipboard-enabled="false"
  type="number"
  visible-as="password"
  name="verificationCode"
  required
  aria-label="Verification code"
  aria-describedby="verification-error"
></wts-otp>
<p id="verification-error">Enter all six digits.</p>

Listen for input, change, and complete:

const element = document.querySelector('wts-otp');

element.addEventListener('complete', (event) => {
  console.log(event.detail.value);
});

The element supports value, complete, autoFill, clipboardEnabled, disabled, readOnly, required, focus(), focusDigit(index), reset(), checkValidity(), and reportValidity(). It participates in FormData, form reset, disabled fieldsets, and native required validation.

Tooling and IDEs can discover the Web Component API through the published Custom Elements Manifest:

import manifest from 'wts-otp/custom-elements.json' with { type: 'json' };

Framework integration

Create the core instance after the framework has mounted the host element and call destroy() during cleanup.

React

function OtpInput() {
  const host = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const otp = new WtsOtp(host.current!, { digits: 6 });
    return () => otp.destroy();
  }, []);

  return <div ref={host} />;
}

Vue

const host = ref<HTMLElement>();
let otp: WtsOtp;

onMounted(() => {
  otp = new WtsOtp(host.value!, { digits: 6 });
});

onUnmounted(() => otp.destroy());

The same lifecycle pattern works in Angular, Svelte, Solid, and other browser frameworks. No framework adapter is required.

Options

| Option | Default | Purpose | | --- | --- | --- | | digits | 4 | Number of OTP characters, from 1 to 32 | | type | "number" | Numeric or unrestricted text input | | visibleAs | "text" | Display characters as text or password | | placeholder | "" | Placeholder for empty inputs | | borderOn | "all-side" | Visible border side | | autofocus | false | Focus the first empty character after mounting | | autocomplete | "one-time-code" | Browser autofill hint for the first input | | autoFill | true | Allow browser and device verification-code autofill suggestions | | clipboardEnabled | true | Allow copy, cut, and paste interactions | | value | "" | Initial value | | disabled | false | Disable the input group | | readOnly | false | Prevent user edits without disabling the group | | required | false | Require every character for native validation | | showError | false | Apply invalid state and styling | | name | "otp" | Native form field name | | ariaLabel | "One-time password" | Accessible group label | | ariaDescribedBy | "" | IDs of descriptions or error messages | | injectStyles | true | Inject default scoped styles |

Methods

otp.setValue('1234');
otp.setOptions({ digits: 6, showError: true });
otp.setDisabled(true);
otp.setError(true);
otp.focus(0);
otp.reset();
otp.destroy();

setValue(value, true) emits both the input change and completion callback when the new value is complete. Multi-character values delivered by OTP autofill or paste fill from the first character even when another character is focused. A one-character paste replaces only the focused character. Pasted content with no valid characters leaves the current value unchanged.

Set clipboardEnabled: false to prevent copy, cut, and paste. Set autoFill: false to apply autocomplete="off" to every character input. The equivalent Web Component attributes are clipboard-enabled="false" and auto-fill="false". Verification-code suggestions remain controlled by the browser and operating system.

Styling

Default styles are injected when an instance is created. Set injectStyles: false and import wts-otp/styles.css when inline styles are restricted by a Content Security Policy.

Both the new prefixed variables and the original variables are supported:

.verification-code {
  --wts-otp-border: 2px solid #545353;
  --wts-otp-border-radius: 8px;
  --wts-otp-input-width: 52px;
  --wts-otp-input-height: 52px;
  --wts-otp-gap: 8px;
  --wts-otp-focus-border-color: #2563eb;
  --wts-otp-invalid-border-color: #dc2626;
}

Browser and release policy

The distributed ESM and CommonJS bundles target ES2020. Before publishing a release, run:

npm run test:browser:install
npm run check

Development and browser testing use Node.js 22. npm run check runs the unit suite, creates the distribution bundles, and executes Playwright in Chromium, Firefox, and WebKit. To run only the browser suite or open Playwright's interactive runner:

npm run test:browser
npm run test:browser:ui

The unit suite covers DOM behavior, Web Component lifecycle, native form fallback, accessibility state, autofill, paste, and packaging style consistency. The real-browser suite also covers reconnects, fieldset disabled state, validation recovery, focus preservation, reset behavior, narrow mobile layout, and paste distribution. The same matrix runs automatically in CI for changes to this package.

Supply-chain security

The package has no runtime dependencies. Development dependencies are locked with package-lock.json, CI installs them with npm ci, and release builds disable dependency lifecycle scripts during installation.

Production releases use a short-lived, package-scoped granular npm token stored only in a protected GitHub deployment environment. The release workflow:

  • runs vulnerability and registry-signature audits;
  • runs all unit and real-browser tests before packaging;
  • separates unprivileged verification from the token-enabled publish job;
  • transfers only the verified package tarball to the publish job;
  • uses read-only GitHub permissions;
  • pins every GitHub Action to a full commit SHA; and
  • requires a matching version tag and protected npm deployment environment.

See RELEASING.md for the required one-time npm and GitHub configuration. Report vulnerabilities according to SECURITY.md.

Migration

Version 20 replaces the former Angular component with the framework-agnostic core. See MIGRATION.md for the v19 to v20 migration.