New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@verino/vanilla

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@verino/vanilla

Vanilla JS adapter for Verino. Reliable OTP inputs from a single core.

latest
Source
npmnpm
Version
2.1.0
Version published
Maintainers
1
Created
Source
verino

@verino/vanilla

Vanilla JS adapter for Verino. Reliable OTP inputs from a single core.

Live demo  npm version  gzip size  Zero dependencies  TypeScript

Overview

@verino/vanilla is the DOM adapter for @verino/core. It takes the pure state machine and provides everything needed to run it in the browser: slot rendering, hidden input wiring, data-* attribute management, CSS custom properties, and an optional plugin system.

initOTP(target, options) mounts a fully accessible OTP field into any container element (CSS selector or DOM node) and returns a VerinoInstance[] — one instance per matched element. Call instance.destroy() to remove all event listeners, stop active timers, and clean up any DOM elements added by plugins.

The adapter includes three tree-shakeable plugins as separate entry points. Import only what you need:

  • timerUIPlugin — renders a live countdown badge and a cooldown-aware resend button inside the field.
  • webOTPPlugin — intercepts incoming SMS OTPs via the Web OTP API (navigator.credentials.get) and fills the slots automatically.
  • pmGuardPlugin — detects credential badge overlays injected by password managers (LastPass, 1Password, Dashlane, Bitwarden, Keeper) and repositions them to avoid covering the slot group.

Why Use This Adapter?

  • No framework required. Works in plain HTML, server-rendered apps, and bundled projects.
  • Plugin system. Tree-shakeable plugins for timer UI, Web OTP API, and password manager guards.
  • CDN-ready. A pre-built IIFE bundle works without any build tooling.
  • Fully programmatic. Control state with setError, setSuccess, setDisabled, reset, focus, and getCode.

Installation

# npm
npm i @verino/vanilla

# pnpm
pnpm add @verino/vanilla

# yarn
yarn add @verino/vanilla

Peer dependency: @verino/core installs automatically.

CDN

<script src="https://unpkg.com/@verino/vanilla/dist/verino.min.js"></script>

The IIFE bundle exposes window.Verino. Call Verino.init(...) directly.

Quick Start

<div class="verino-wrapper"></div>

<script type="module">
  import { initOTP } from '@verino/vanilla'

  const [otp] = initOTP('.verino-wrapper', {
    length:     6,
    type:       'numeric',
    onComplete: (code) => console.log('Done:', code),
  })
</script>

Note: verify(code) and similar functions used in examples are placeholders — replace them with your own API calls or application logic.

initOTP returns VerinoInstance[]. Call instance.destroy() to clean up event listeners and DOM elements.

Common Patterns

Use caseKey options
SMS / email OTPtype: 'numeric', timer: 60, onResend
TOTP / 2FA with separatorseparatorAfter: 3
PIN entrymasked: true, blurOnComplete: true
Alphanumeric codetype: 'alphanumeric', pasteTransformer
Invite / referral codeseparatorAfter: [3, 6], pattern: /^[A-Z0-9]$/
Hex activation keypattern: /^[0-9A-F]$/
Async verification locksetDisabled(true / false) around the API call
Native form submissionname: 'otp_code'
Pre-fill on mountdefaultValue: '123456'
Display-only / read-onlyreadOnly: true

Usage

haptic and sound are mount-configured in the vanilla adapter. If you need to change feedback behavior at runtime, destroy and re-initialise the instance with the new options.

Async verification

const [otp] = initOTP('.verino-wrapper', {
  length: 6,
  onComplete: async (code) => {
    otp.setDisabled(true)
    const ok = await api.verify(code)
    otp.setDisabled(false)
    ok ? otp.setSuccess(true) : otp.setError(true)
  },
})

Timer and resend

Pass timer and onResend — the adapter renders the countdown badge and resend button automatically:

const [otp] = initOTP('.verino-wrapper', {
  length:   6,
  timer:    60,
  onResend: () => requestNewCode(),
  onExpire: () => showExpiredMessage(),
})

Provide an onTick callback to drive a custom timer UI and disable the built-in footer:

const [otp] = initOTP('.verino-wrapper', {
  timer:    60,
  onTick:   (remaining) => (timerEl.textContent = `0:${String(remaining).padStart(2, '0')}`),
  onExpire: () => showResendButton(),
  onResend: () => { otp.resend(); hideResendButton() },
})

Masked input

initOTP('.verino-wrapper', {
  masked:   true,
  maskChar: '●',  // default;
})

Or via attribute: <div class="verino-wrapper" data-masked>.

Paste transformer

Strip formatting from real-world OTP SMS messages before distributing to slots:

initOTP('.verino-wrapper', {
  pasteTransformer: (raw) => raw.replace(/[\s-]/g, ''),
  // 'G-123456' → '123456', '123 456' → '123456'
})

Plugins

Plugins are separate entry points — import only what you need:

import { initOTP }        from '@verino/vanilla'
import { timerUIPlugin }  from '@verino/vanilla/plugins/timer-ui'
import { webOTPPlugin }   from '@verino/vanilla/plugins/web-otp'
import { pmGuardPlugin }  from '@verino/vanilla/plugins/pm-guard'
PluginEntry pointWhat it does
timerUIPlugin@verino/vanilla/plugins/timer-uiRenders countdown badge + cooldown-aware resend button
webOTPPlugin@verino/vanilla/plugins/web-otpIntercepts SMS OTP via navigator.credentials.get and auto-fills
pmGuardPlugin@verino/vanilla/plugins/pm-guardRepositions LastPass, 1Password, Dashlane, Bitwarden, Keeper badge overlays

CDN usage

<script src="https://unpkg.com/@verino/vanilla/dist/verino.min.js"></script>
<div class="verino-wrapper"></div>
<script>
  Verino.init('.verino-wrapper', {
    length:     6,
    onComplete: (code) => console.log(code),
  })
</script>

data-* state attributes

Each slot element receives data-* attributes reflecting the current state of the machine. Style the field entirely with CSS, no JavaScript class management required

Slot attributes

Slot-level attributes use string values ("true" / "false"):

AttributeMeaning
data-activeLogical cursor is at this slot (set even when the field is blurred)
data-focusBrowser focus is on the hidden input
data-filledSlot contains a character
data-emptySlot is unfilled (complement of data-filled)
data-invalidError state is active
data-successSuccess state is active (mutually exclusive with data-invalid)
data-disabledField is disabled
data-readonlyField is in read-only mode
data-completeAll slots are filled
data-maskedMasked mode is active
data-firstThis is the first slot 0
data-lastThis is the last slot
data-slotZero-based position of the slot as a string ("0", "1", …)

Wrapper attributes

Set on the wrapper element as boolean presence attributes (no value):

AttributeWhen present
data-completeAll slots are filled
data-invalidError state is active
data-successSuccess state is active
data-disabledField is disabled
data-readonlyField is read-only
/* Slot-level — scope to your field with an id or class prefix */
[data-active="true"][data-focus="true"] { border-color: #2A2A2A; }
[data-filled="true"]                    { background:   #FFFFFF; }
[data-empty="true"]                     { background:   #FAFAFA; }
[data-invalid="true"]                   { border-color: #FF3846; }
[data-success="true"]                   { border-color: #00C65B; }
[data-disabled="true"]                  { opacity: 0.45; pointer-events: none; }
[data-readonly="true"]                  { cursor: default; }
[data-masked="true"]                    { letter-spacing: 0.15em; }
[data-complete="true"]                  { border-color: #00C65B; }

/* Wrapper-level (boolean presence selectors) */
.verino-wrapper[data-complete]  { outline: 2px solid #00C65B; }
.verino-wrapper[data-invalid]   { outline: 2px solid #FF3846; }
.verino-wrapper[data-disabled]  { opacity: 0.6; }

/* Connected pill layout */
[data-first="true"]                     { border-radius: 8px 0 0 8px; }
[data-last="true"]                      { border-radius: 0 8px 8px 0; }
[data-first="false"][data-last="false"] { border-radius: 0; }

/* Target a specific slot by index */
[data-slot="0"] { font-weight: 700; }

Verino automatically adds these CSS classes to the rendered DOM:

  • .verino-slot — each slot
  • .verino-caret — blinking caret
  • .verino-separator — group separator

Footer (added by timerUIPlugin):

  • .verino-timer — countdown container
  • .verino-timer-badge — timer badge
  • .verino-resend — resend container
  • .verino-resend-btn — resend button

State is always exposed via data-* attributes, not class names.

CSS Custom Properties

Style the field using --verino-* CSS custom properties on the wrapper element:

.verino-wrapper  {
  /* Dimensions */
  --verino-size:          56px;
  --verino-gap:           12px;
  --verino-radius:        10px;
  --verino-font-size:     24px;

  /* Colors */
  --verino-bg:            #FAFAFA;
  --verino-bg-filled:     #FFFFFF;
  --verino-color:         #0C0C0C;
  --verino-border-color:  #DBDBDB;
  --verino-active-color:  #2A2A2A;
  --verino-error-color:   #FF3846;
  --verino-success-color: #00C65B;
  --verino-caret-color:   #2A2A2A;

  /* Placeholder, separator & mask */
  --verino-placeholder-color: #888888;
  --verino-placeholder-size:  16px;
  --verino-separator-color:   #B2B2B2;
  --verino-separator-size:    18px;
  --verino-masked-size:       16px;
}

Accessibility

  • Single ARIA-labelled input — the hidden input carries aria-label="Enter your N-digit code" (or N-character code for non-numeric types). Screen readers announce one field, not multiple slots.
  • Visual slots stay hidden from assistive technology — the real input remains the single editing control.
  • Purposeful timer announcements — ticking uses role="timer" with live updates off; expiry exposes one polite resend status.
  • State wiring — aria-invalid and aria-describedby follow validation and timer/resend state.
  • inputMode — set to "numeric" or "text" based on type, triggering the correct mobile keyboard.
  • autocomplete="one-time-code" — enables native SMS autofill on iOS and Android.
  • Anti-interference — spellcheck="false", autocorrect="off", and autocapitalize="off" prevent unwanted browser input behavior.
  • maxLength — constrains the hidden input to length, preventing overflow from IME and composition events.
  • type="password" in masked mode — enables secure input and triggers the password keyboard on mobile.
  • Native form integration — the name option includes the hidden input in <form> submission and FormData.
  • Keyboard navigation — full support for ←, →, Backspace, Delete, and Tab.

API Reference

initOTP(target, options?)

function initOTP(
  target: string | HTMLElement,
  options?: VanillaOTPOptions
): VerinoInstance[]

target can be a CSS selector string or a DOM element.

VanillaOTPOptions

All OTPOptions from @verino/core, plus:

OptionTypeDefaultDescription
separatorAfternumber | number[]—Insert a visual separator after these slot indices
separatorstring'—'Separator character
maskedbooleanfalseShow mask glyph in filled slots
maskCharstring'●'Glyph used in masked mode
onChange(code: string) => void—Fires on every input change
resendAfternumber30Resend button cooldown in seconds
otpTransportOTPTransport | falseWeb OTPCustom automatic-code receiver, or disable retrieval
otpTransportTimeoutnumber300000Automatic-code timeout in milliseconds
messagesPartial<OTPUIStrings>EnglishBuilt-in labels, timer, and resend copy

VerinoInstance methods

MethodDescription
timerLive TimerController (pause, resume, snapshots, deadline updates, and subscriptions)
getCode()Return the current assembled value
reset()Clear all slots, restart timer, re-focus
resend()Reset + fire onResend callback
setError(v)Toggle error state
setSuccess(v)Toggle success state; stops timer
setDisabled(v)Lock or unlock the field
setReadOnly(v)Toggle read-only mode
focus(i?)Focus slot i (defaults to first empty slot)
destroy()Remove all event listeners and clean up DOM

Compatibility

EnvironmentRequirement
@verino/coreSame monorepo release
BrowsersAll evergreen; IE not supported
Node.js (SSR)≥ 18 (DOM-free core only)
TypeScript≥ 5.0
Module formatESM (no CJS build for vanilla)

Integration with Core

@verino/vanilla calls createOTP() from @verino/core internally. Character filtering, cursor logic, paste normalization, and event routing live in core; countdown, feedback, scheduling, and toolkit helpers come from @verino/core/toolkit. The vanilla adapter handles DOM construction, attribute management, and plugin lifecycle.

See the @verino/core README for the full state machine and event reference.

Contributing

This package lives in the verino monorepo. See CONTRIBUTING.md for guidelines.

# Clone and install
git clone https://github.com/boastack/verino.git
cd verino && pnpm i

# Run before opening a PR
pnpm --filter @verino/vanilla build && pnpm test

Ecosystem

PackagePurpose
@verino/coreOTP state machine + toolkit
@verino/reactuseOTP hook + HiddenOTPInput component (React ≥ 18)
@verino/vueuseOTP composable with reactive Vue refs (Vue ≥ 3)
@verino/svelteuseOTP store + use:action directive (Svelte ≥ 4)
@verino/alpineVerinoAlpine plugin — x-verino directive (Alpine.js ≥ 3)
@verino/web-component<verino-input> Shadow DOM custom element

License

MIT © 2026 Olawale Balo

Keywords

otp

FAQs

Package last updated on 21 Jul 2026

Related posts