Built by @Olawale Balo — Product Designer + Design Engineer
verino is a headless OTP and verification code input library. The core is a zero-dependency, zero DOM TypeScript state machine with an event system. Each framework package wraps that core using idiomatic primitives such as a hook, composable, store and action, directive, or custom element, without reimplementing any logic.
Every adapter shares the same single hidden input architecture. One real <input> captures keyboard events, native SMS autofill with autocomplete="one-time-code", and the Web OTP API, while visual slot elements are purely decorative and driven by data-* attributes.
| Package | Description | Version |
|---|---|---|
verino |
Core state machine + vanilla DOM adapter | |
@verino/react |
useOTP hook + HiddenOTPInput component |
|
@verino/vue |
useOTP composable |
|
@verino/svelte |
useOTP store + action |
|
@verino/alpine |
x-verino directive |
|
@verino/web-component |
<verino-input> custom element |
verino (core + vanilla adapter)
├── createOTP() ← pure state machine, zero DOM
├── initOTP() ← vanilla DOM adapter
└── adapters/
└── plugins/
├── timerUIPlugin ← built-in countdown + resend UI
├── webOTPPlugin ← SMS autofill via Web OTP API
└── pmGuardPlugin ← password manager badge guard
@verino/react → useOTP hook + HiddenOTPInput
@verino/vue → useOTP composable
@verino/svelte → useOTP store + Svelte action
@verino/alpine → VerinoAlpine plugin (x-verino directive)
@verino/web-component → VerinoInput custom element (<verino-input>)
All adapter packages import createOTP from verino. No adapter reimplements event handling, character filtering, cursor movement, paste logic, or timer management.
<input> overlays the visual slots, capturing keyboard input, paste, SMS autofill, Web OTP API, password managers, screen readers, and IME natively.role="group", aria-labelledby, per-slot screen reader labels, inputMode, and autocomplete="one-time-code" are built in, with all visual slots marked aria-hidden.setError, setSuccess, setDisabled, setReadOnly, reset(), focus(i), and getCode() are available across every adapter for seamless integration with async flows and external state.timer: 60 for a live countdown badge and cooldown-aware resend button wired to reset(), or provide onTick to power custom UI with the same timer logic.OTPEvent discriminated union alongside state (INPUT, DELETE, PASTE, COMPLETE, RESET, ERROR, SUCCESS, and more) for precise, event-driven reactions.MutationObserver before they overlap your slots.| Feature | verino | input-otp | react-otp-input |
|---|---|---|---|
| Pure headless state machine | ✅ | ✗ | ✗ |
| Typed event system | ✅ | ✗ | ✗ |
| Web OTP API (SMS intercept) | ✅ | ✗ | ✗ |
| Built-in timer and resend | ✅ | ✗ | ✗ |
| Masked mode | ✅ | ✗ | ✗ |
Programmatic API (setError, setSuccess, reset, focus) |
✅ | ✗ | ✗ |
| Haptic and sound feedback | ✅ | ✗ | ✗ |
blurOnComplete |
✅ | ✗ | ✗ |
onInvalidChar callback |
✅ | ✗ | ✗ |
readOnly mode |
✅ | ✗ | ✗ |
data-* state attributes |
✅ | ✗ | ✗ |
| CSS variable theming | ✅ | ✗ | ✗ |
| Vanilla JS | ✅ | ✗ | ✗ |
| Vue | ✅ | ✗ | ✗ |
| Svelte | ✅ | ✗ | ✗ |
| Alpine.js | ✅ | ✗ | ✗ |
| Web Component | ✅ | ✗ | ✗ |
| React | ✅ | ✅ | ✅ |
| Single hidden input | ✅ | ✅ | ✗ |
| Password manager guard | ✅ | ✅ | ✗ |
| Zero dependencies | ✅ | ✅ | ✗ |
| TypeScript | ✅ | ✅ | ✅ |
Install only the adapter for your framework — verino is declared as a dependency and installed automatically:
# React
npm i @verino/react
# Vue
npm i @verino/vue
# Svelte
npm i @verino/svelte
# Alpine.js
npm i @verino/alpine
# Web Component
npm i @verino/web-component
# Vanilla JS
npm i verino
A pre-built IIFE bundle exposes window.Verino.init, identical to initOTP:
<script src="https://unpkg.com/verino/dist/verino.min.js"></script>
<div class="verino-wrapper"></div>
<script>
Verino.init('.verino-wrapper', { onComplete: (code) => console.log(code) })
</script>
Alpine.js CDN bundle — exposes window.VerinoAlpine:
<script defer src="https://unpkg.com/alpinejs"></script>
<script src="https://unpkg.com/verino/dist/verino-alpine.min.js"></script>
<script>
document.addEventListener('alpine:init', () => Alpine.plugin(VerinoAlpine))
</script>
Web Component CDN bundle — auto-registers <verino-input>:
<script src="https://unpkg.com/verino/dist/verino-wc.min.js"></script>
<verino-input length="6" name="otp"></verino-input>
<script>
document.querySelector('verino-input').addEventListener('complete', (e) => console.log(e.detail.code))
</script>
import { useOTP, HiddenOTPInput } from '@verino/react'
function OTPField() {
const otp = useOTP({ length: 6, onComplete: (code) => verify(code) })
return (
<div style={{ position: 'relative', display: 'inline-flex', gap: 8 }}>
<HiddenOTPInput {...otp.hiddenInputProps} />
{otp.getSlots().map((slot) => {
const { char, isActive, isFilled, isError, hasFakeCaret, placeholder } = otp.getSlotProps(slot.index)
return (
<div
key={slot.index}
className={['slot', isActive && 'is-active', isFilled && 'is-filled', isError && 'is-error'].filter(Boolean).join(' ')}
>
{hasFakeCaret && <span className="caret" />}
{isFilled ? char : placeholder}
</div>
)
})}
</div>
)
}
<div class="verino-wrapper" data-length="6" data-timer="60"></div>
<script type="module">
import { initOTP } from 'verino'
const [otp] = initOTP('.verino-wrapper', {
onComplete: (code) => verify(code),
})
</script>
<script type="module" src="https://unpkg.com/@verino/web-component"></script>
<verino-input length="6" timer="60"></verino-input>
<script>
document.querySelector('verino-input')
.addEventListener('complete', (e) => verify(e.detail.code))
</script>
| Pattern | Key options |
|---|---|
| SMS / email OTP | type: 'numeric', timer: 60, onResend |
| 2FA / TOTP with grouping | 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 API call |
| Native form submission | name: 'otp_code' |
| Pre-fill on mount | defaultValue: '123456' |
| Display-only field | readOnly: true |
This repository is a pnpm workspace managed by Turborepo.
pnpm install # install dependencies
pnpm build # build all packages
pnpm build:cdn # build CDN bundles
pnpm test # run unit tests
pnpm typecheck # type-check all packages
pnpm dev # watch mode
verino/
├── packages/
│ ├── verino/ # core + vanilla adapter
│ ├── react/
│ ├── vue/
│ ├── svelte/
│ ├── alpine/
│ └── web-component/
├── tests/ # unit, SSR, and e2e tests
├── examples/ # per-framework demos
└── dist/ # CDN bundles (generated)
See CONTRIBUTING.md for guidelines. Issues and pull requests are welcome.
# Run all tests before submitting a PR
pnpm test
pnpm typecheck
MIT © 2026 Olawale Balo