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/alpine

v2.0.0

Published

Alpine.js adapter for Verino. Reliable OTP inputs from a single core.

Readme


Overview

@verino/alpine registers the VerinoAlpine plugin, which provides the x-verino directive. Attach it to any container element with a configuration expression, and Verino automatically builds the full OTP field (hidden input, visual slots, optional separators, countdown timer, and resend button) with no template authoring required.

The expression is evaluated in the Alpine component scope and kept in sync through Alpine's reactivity, so $data references keep working after the directive is created. Programmatic runtime control is available on el._verino from any JavaScript context.

When Alpine destroys the component (via x-if or x-for), destroy() is called automatically via Alpine's cleanup() hook.


Why Use This Adapter?

  • Zero template work. One x-verino directive renders the full OTP field, including visual slots, caret, separators, and timer.
  • Reactive Alpine data. The x-verino expression evaluates in Alpine’s component scope — $data properties work seamlessly.
  • Automatic cleanup. No manual teardown is needed when the element is removed via x-if.
  • CDN-ready. A pre-built IIFE bundle works out of the box, with no bundler required.

Installation

npm

# npm
npm i @verino/alpine

# pnpm
pnpm add @verino/alpine

# yarn
yarn add @verino/alpine

Peer dependency: Alpine.js ≥ 3. @verino/core installs automatically.

Register the plugin before Alpine.start():

import Alpine from 'alpinejs'
import { VerinoAlpine } from '@verino/alpine'

Alpine.plugin(VerinoAlpine)
Alpine.start()

CDN

<script defer src="https://unpkg.com/alpinejs"></script>
<script src="https://unpkg.com/@verino/alpine/dist/verino-alpine.min.js"></script>
<script>
  document.addEventListener('alpine:init', () => Alpine.plugin(VerinoAlpine))
</script>

Quick Start

<div
  x-data
  x-verino="{ length: 6, onComplete: (code) => verify(code) }"
></div>

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

Verino renders the complete OTP field inside the element, wires all event handlers, and manages all state internally.


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

Reactive Alpine data

The x-verino expression is evaluated in the Alpine component scope — reactive $data properties work directly:

<div
  x-data="{ timer: 60 }"
  x-verino="{ length: 6, timer: timer, onComplete: (code) => verify(code) }"
></div>

Timer and resend

When timer is set and no onTick is provided, verino renders a "Code expires in 1:00" footer and a "Didn't receive the code? Resend" section automatically:

<div x-verino="{ length: 6, timer: 60, onResend: () => sendCode() }"></div>

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

<div
  x-data="{ remaining: 60 }"
  x-verino="{ length: 6, timer: 60, onTick: (r) => (remaining = r) }"
></div>
<p x-text="`Expires in ${remaining}s`"></p>

Programmatic control via el._verino

<div id="otp-field" x-verino="{ length: 6, onComplete: handleComplete }"></div>

<script>
  const field = document.getElementById('otp-field')

  async function handleComplete(code) {
    field._verino.setDisabled(true)
    const ok = await api.verify(code)
    field._verino.setDisabled(false)
    ok ? field._verino.setSuccess(true) : field._verino.setError(true)
  }
</script>

Separator

<!-- Single separator: [*][*][*] — [*][*][*] -->
<div x-verino="{ length: 6, separatorAfter: 3 }"></div>

<!-- Multiple separators: [*][*] — [*][*] — [*][*] — [*][*] -->
<div x-verino="{ length: 8, separatorAfter: [2, 4, 6] }"></div>

Masked input

<div x-verino="{ length: 6, masked: true, maskChar: '●' }"></div>

data-* state attributes

Since verino renders the full DOM inside the directive’s container, all data-* attributes are applied automatically — no additional template work is required.

Slot attributes

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

| 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", …) |

Wrapper attributes

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

| 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 |

/* Slot-level — scope to your field with an id or class prefix */
[data-active="true"][data-focus="true"] { border-color: #3D3D3D; }
[data-filled="true"]                    { background:   #FFFFFF; }
[data-empty="true"]                     { background:   #FAFAFA; }
[data-invalid="true"]                   { border-color: #FB2C36; }
[data-success="true"]                   { border-color: #00C950; }
[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: #00C950; }

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

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

/* Target a specific slot by index */
.verino-slot[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-field {
  /* 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;
}

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

Plugin registration

import { VerinoAlpine } from '@verino/alpine'
Alpine.plugin(VerinoAlpine)   // call before Alpine.start()

x-verino expression options

The directive accepts the core machine options from @verino/core, plus these Alpine/browser helper options:

| Option | Type | Default | Description | |---|---|---|---| | separatorAfter | number \| number[] | — | Separator position(s) | | separator | string | '—' | Separator character | | onChange | (code: string) => void | — | Fires on INPUT, DELETE, CLEAR, PASTE | | onTick | (remaining: number) => void | — | Custom tick callback; suppresses built-in timer UI | | resendAfter | number | 30 | Resend button cooldown in seconds | | masked | boolean | false | Show mask glyph in filled slots | | maskChar | string | '●' | Mask glyph |

el._verino

Exposed on the wrapper element after mount:

el._verino = {
  getCode():                          string
  getSlots():                         SlotEntry[]
  getInputProps(slotIndex: number):   InputProps
  reset():                            void   // clear slots + restart timer + re-focus
  resend():                           void   // reset + fire onResend
  setError(v: boolean):               void
  setSuccess(v: boolean):             void   // stops timer on success
  setDisabled(v: boolean):            void
  setReadOnly(v: boolean):            void
  focus(slotIndex: number):           void
  destroy():                          void   // stop timers + remove footer elements
}

Call el._verino.destroy() before manually removing the element from the DOM. When Alpine destroys the component via x-if or x-for, destroy() is called automatically via Alpine's cleanup() hook.


Compatibility

| Environment | Requirement | |---|---| | Alpine.js | ≥ 3 | | @verino/core | Same monorepo release | | TypeScript | ≥ 5.0 | | Browsers | All evergreen | | Module format | ESM + IIFE (CDN) |


Integration with Core

VerinoAlpine calls createOTP() from @verino/core when the x-verino directive mounts. Character filtering, cursor logic, paste normalization, and event routing live in core; countdown, feedback, scheduling, and toolkit helpers come from @verino/core/toolkit.

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/alpine 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/web-component | <verino-input> Shadow DOM custom element |


License

MIT © 2026 Olawale Balo