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

@verino/web-component

v2.0.0

Published

Web Component adapter for Verino. Reliable OTP inputs from a single core.

Readme


Overview

@verino/web-component registers <verino-input>, a self-contained custom element that renders a complete OTP input field inside a Shadow DOM. Drop it in HTML and it works with no framework, no build step, and no template authoring required. Import the package once and <verino-input> is available everywhere.

Structural attribute changes rebuild the shadow DOM, while runtime attributes like disabled, readonly, name, and placeholder patch in place. Existing code is preserved across rebuilds. CSS custom properties cascade through the shadow root, theming works from outside with no special configuration. All custom events are composed: true and cross the shadow boundary automatically.


Why Use This Adapter?

  • Framework-agnostic. Works in plain HTML, React, Vue, Svelte, or any other environment that can render HTML.
  • No peer dependencies. Self-registers on import — no wrapper component needed.
  • Attribute-driven. All common options are HTML attributes, including booleans and timer config.
  • Fully themeable. CSS custom properties cascade through the shadow root.

Installation

npm

# npm
npm i @verino/web-component

# pnpm
pnpm add @verino/web-component

# yarn
yarn add @verino/web-component

No peer dependencies. Import the package to auto-register <verino-input>:

import '@verino/web-component'

CDN

<script src="https://unpkg.com/@verino/web-component/dist/verino-wc.min.js"></script>

Quick Start

<verino-input length="6"></verino-input>

<script>
  document.querySelector('verino-input')
    .addEventListener('complete', (e) => verify(e.detail.code))
</script>

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


Common Patterns

| Use case | Key options | |---|---| | SMS / email OTP | type: 'numeric', timer: 60, onResend | | TOTP / 2FA with separator | separatorAfter: 3 | | PIN entry | masked: true, blurOnComplete: true | | Alphanumeric code | type: 'alphanumeric', pasteTransformer | | Invite / referral code | separatorAfter: [3, 6], pattern: /^[A-Z0-9]$/ | | Hex activation key | pattern: /^[0-9A-F]$/ | | Async verification lock | setDisabled(true / false) around the API call | | Native form submission | name: 'otp_code' | | Pre-fill on mount | defaultValue: '123456' | | Display-only / read-only | readOnly: true |


Usage

Attributes

Boolean attributes (disabled, readonly, masked, blur-on-complete, select-on-focus, sound) follow standard HTML semantics — presence = true, absence = false. auto-focus and haptic default to true when absent; set to "false" to opt out.

<verino-input
  length="6"
  type="numeric"
  timer="60"
  resend-after="30"
  separator-after="3"
  separator="—"
  placeholder="○"
  name="otp_code"
  masked
  blur-on-complete
></verino-input>

| Attribute | Type | Default | Description | |---|---|---|---| | length | number | 6 | Number of slots | | type | numeric \| alphabet \| alphanumeric \| any | numeric | Character set | | timer | number | 0 | Countdown in seconds (0 = no timer) | | resend-after | number | 30 | Resend button cooldown in seconds | | disabled | boolean | false | Disable all input | | readonly | boolean | false | Block mutations, preserve navigation | | separator-after | string | — | Slot position(s) for separator — "3" or "2,4" | | separator | string | | Separator character | | masked | boolean | false | Show mask glyph in filled slots | | mask-char | string | | Glyph used when masked is set | | name | string | — | Hidden input name for native form submission | | placeholder | string | — | Character shown in empty slots | | auto-focus | "false" | true | Set to "false" to suppress focus on mount | | select-on-focus | boolean | false | Select current slot character on focus | | blur-on-complete | boolean | false | Blur input when all slots are filled | | default-value | string | — | Pre-fill slots on mount; does not fire complete | | id-base | string | — | Stable prefix for request-scoped ids | | sound | boolean | false | Play audio tone on completion | | haptic | "false" | true | Set to "false" to suppress vibration feedback |

auto-focus and haptic default to true when absent. Set the attribute to "false" to opt out.

Events

All events bubble and are composed: true — they cross the shadow boundary without any special setup:

const el = document.querySelector('verino-input')

// All slots filled
el.addEventListener('complete', (e) => verify(e.detail.code))

// Timer reached zero
el.addEventListener('expire', () => console.log('Code expired'))

// Every input change — fires on typing, backspace, paste
el.addEventListener('change', (e) => console.log(e.detail.code))

JS-only properties

Options that cannot be expressed as HTML attributes (functions and RegExp) are set as JavaScript properties:

const el = document.querySelector('verino-input')

el.pattern          = /^[0-9A-F]$/
el.pasteTransformer = (raw) => raw.replace(/[\s-]/g, '')
el.onComplete       = (code) => verify(code)
el.onResend         = () => sendCode()
el.onFocus          = () => showHelp()
el.onBlur           = () => hideHelp()
el.onInvalidChar    = (char, index) => shake(index)

Async verification

Use the DOM API to lock the field during an async call:

const el = document.querySelector('verino-input')

el.addEventListener('complete', async ({ detail: { code } }) => {
  el.setDisabled(true)
  const ok = await verify(code)
  el.setDisabled(false)
  ok ? el.setSuccess(true) : el.setError(true)
})

Separator

<!-- Single: [*][*][*] — [*][*][*] -->
<verino-input length="6" separator-after="3" separator="—"></verino-input>

<!-- Multiple: [*][*] — [*][*] — [*][*] — [*][*] -->
<verino-input length="8" separator-after="2,4,6"></verino-input>

Masked input

<verino-input length="6" masked mask-char="●"></verino-input>

data-* state attributes

The web component exposes state in two places: the host element (<verino-input> itself, targetable from outside the shadow root) and shadow slot elements (inside the shadow DOM).

Host attributes

Set on <verino-input> itself as boolean presence attributes (no value) — target from outside the shadow root:

| Attribute | When present | |---|---| | data-complete | All slots are filled | | data-invalid | Error state is active | | data-success | Success state is active | | data-disabled | Field is disabled | | data-readonly | Field is read-only |

/* Host element attributes — target from outside */
verino-input[data-complete] { outline: 2px solid #00C950; }
verino-input[data-invalid]  { outline: 2px solid #FB2C36; }
verino-input[data-success]  { outline: 2px solid #00C950; }
verino-input[data-disabled] { opacity: 0.6; }
verino-input[data-readonly] { cursor: default; }

Slot attributes (shadow DOM)

Inside the shadow root, slot elements receive string-value attributes ("true" / "false"). These drive the built-in shadow stylesheet and are accessible via getInputProps(index):

| Attribute | Meaning | |---|---| | data-active | Logical cursor is at this slot (set even when the field is blurred) | | data-focus | Browser focus is on the hidden input | | data-filled | Slot contains a character | | data-empty | Slot is unfilled (complement of data-filled) | | data-invalid | Error state is active | | data-success | Success state is active (mutually exclusive with data-invalid) | | data-disabled | Field is disabled | | data-readonly | Field is in read-only mode | | data-complete | All slots are filled | | data-masked | Masked mode is active | | data-first | This is the first slot 0 | | data-last | This is the last slot | | data-slot | Zero-based position of the slot as a string ("0", "1", …) |

/* Applied inside the shadow root via the built-in shadow stylesheet */
.verino-wc-slot[data-active="true"][data-focus="true"] { border-color: #3D3D3D; }
.verino-wc-slot[data-filled="true"]                    { background:   #FFFFFF; }
.verino-wc-slot[data-empty="true"]                     { background:   #FAFAFA; }
.verino-wc-slot[data-invalid="true"]                   { border-color: #FB2C36; }
.verino-wc-slot[data-success="true"]                   { border-color: #00C950; }
.verino-wc-slot[data-disabled="true"]                  { opacity: 0.45; pointer-events: none; }
.verino-wc-slot[data-readonly="true"]                  { cursor: default; }
.verino-wc-slot[data-masked="true"]                    { letter-spacing: 0.15em; }
.verino-wc-slot[data-complete="true"]                  { border-color: #00C950; }

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

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

Verino automatically adds these CSS classes to the shadow DOM:

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

Footer (built into the element when timer is set):

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

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


CSS custom properties

CSS custom properties cascade through the shadow root; set them on verino-input externally:

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

  /* Colors */
  --verino-bg:            #FAFAFA;
  --verino-bg-filled:     #FFFFFF;
  --verino-color:         #0A0A0A;
  --verino-border-color:  #E5E5E5;
  --verino-active-color:  #3D3D3D;
  --verino-error-color:   #FB2C36;
  --verino-success-color: #00C950;
  --verino-caret-color:   #3D3D3D;
  --verino-timer-color:   #5C5C5C;

  /* Placeholder, separator & mask */
  --verino-placeholder-color: #D3D3D3;
  --verino-placeholder-size:  16px;
  --verino-separator-color:   #A1A1A1;
  --verino-separator-size:    18px;
  --verino-masked-size:       16px;
  --verino-slot-font:         inherit;
}

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.
  • All visual elements are aria-hidden — slots, separators, caret, and timer UI are removed from the accessibility tree.
  • 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-interferencespellcheck="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

VerinoInput

class VerinoInput extends HTMLElement {
  // Observed attributes — structural changes rebuild, runtime attrs patch in place:
  // length, type, timer, resend-after, disabled, readonly,
  // separator-after, separator, masked, mask-char, name, placeholder,
  // auto-focus, select-on-focus, blur-on-complete, default-value, sound, haptic

  // JS-only setters (cannot be expressed as HTML attributes)
  set pattern(re: RegExp | undefined)
  set pasteTransformer(fn: ((raw: string) => string) | undefined)
  set onComplete(fn: ((code: string) => void) | undefined)
  set onResend(fn: (() => void) | undefined)
  set onFocus(fn: (() => void) | undefined)
  set onBlur(fn: (() => void) | undefined)
  set onInvalidChar(fn: ((char: string, index: number) => void) | undefined)

  // DOM methods
  reset():                        void   // clear slots + restart timer + re-focus
  resend():                       void   // reset + fire onResend + restart with resend cooldown
  setError(v: boolean):           void   // toggle error; clears success
  setSuccess(v: boolean):         void   // toggle success; stops timer; clears error
  setDisabled(v: boolean):        void
  setReadOnly(v: boolean):        void
  get hasSuccess():               boolean

  getCode():                      string
  getSlots():                     SlotEntry[]
  getInputProps(index: number):   InputProps
}

Compatibility

| Environment | Requirement | |---|---| | Browsers | All evergreen (Shadow DOM + Custom Elements v1 required) | | @verino/core | Same monorepo release | | TypeScript | ≥ 5.0 | | Frameworks | Framework-agnostic — works anywhere HTML renders | | Module format | ESM + IIFE (CDN) |


Integration with Core

VerinoInput calls createOTP() from @verino/core when the element connects to the DOM. Character filtering, cursor logic, paste normalization, and event routing live in core; countdown, feedback, scheduling, and toolkit helpers come from @verino/core/toolkit. The custom element handles shadow DOM construction, attribute reflection, and custom event dispatch.

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/web-component build && pnpm test

Ecosystem

| Package | Purpose | |---|---| | @verino/core | OTP state machine + toolkit | | @verino/vanilla | Vanilla DOM adapter + timerUIPlugin, webOTPPlugin, pmGuardPlugin | | @verino/react | useOTP hook + HiddenOTPInput component (React ≥ 18) | | @verino/vue | useOTP composable with reactive Vue refs (Vue ≥ 3) | | @verino/svelte | useOTP store + use:action directive (Svelte ≥ 4) | | @verino/alpine | VerinoAlpine plugin — x-verino directive (Alpine.js ≥ 3) |


License

MIT © 2026 Olawale Balo