wts-otp

Zero-dependency, accessible OTP, PIN, and verification-code input for JavaScript, Angular, React, Vue, Svelte, and Web Components.
1,532
Install
npm install wts-otp
Latest Version:20.0.1
License:MIT
Last Updated:Aug 4, 2026
Links: GitHub  ·  npm
Maintainer: suman_mandal

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.

Related Packages

@teselia/otp

Accessible one-time code input (OTP), shipped as a framework-agnostic Web Compon...

0 0
wts-timepicker

Dependency-free, accessible, framework-agnostic clock-face time picker and Web C...

1,458 0
jervis-icons

Universal icon/image web component that works with any framework

30,773
ff-widget

FeedForward Widget - Universal Web Component for feedback collection. Works with...

2,421
sagegreytechnologies-nkiru-ai-chat-widget-webcomponent

Native Web Component chat widget with complete CSS isolation using Shadow DOM

1,532

Version History

Version Released License
20.0.1 MIT
20.0.0 MIT
19.0.4 MIT
19.0.3 MIT
19.0.2 MIT