OTP Input ^0.1.23
Segmented one-time-code input for SMS verification, 2FA, and PIN entry. Auto-focus, paste distribution, keyboard navigation (arrows, Home/End, Backspace), and autocomplete="one-time-code" for iOS SMS autofill — out of the box. Every part is class-bindable.
1. Basic 6-digit OTP
Default is a 6-digit numeric input. Type anywhere and focus auto-advances.
—<script setup lang="ts">
import { ref } from 'vue'
import { VxOtpInput } from 'veloxui'
const code = ref('')
</script>
<template>
<VxOtpInput v-model="code" />
</template>2. Custom length
Set length to 4 for a PIN, 8 for backup codes, or any value.
<VxOtpInput v-model="pin" :length="4" />
<VxOtpInput v-model="backup" :length="8" />3. With separator
separator="3" inserts a divider every 3 slots — useful for 6-digit codes displayed as XXX-XXX.
<VxOtpInput v-model="code" :length="6" :separator="3" />
<VxOtpInput v-model="code" :length="6" :separator="2" separator-char="·" />4. Masked input
Pass mask to hide the entered digits like a password field. The keyboard stays numeric.
—<VxOtpInput v-model="pin" mask />5. Alphanumeric (text mode)
type="text" accepts any non-whitespace character — great for backup codes or invite codes. Pass allowed-pattern for custom rules (e.g., uppercase letters only).
<VxOtpInput v-model="code" type="text" :length="6" />
<VxOtpInput type="text" :length="5" :allowed-pattern="/^[A-Z]$/" />6. Auto-focus & complete event
auto-focus jumps to the first slot on mount. The complete event fires once all slots are filled — perfect for auto-submit.
<VxOtpInput
v-model="code"
auto-focus
@complete="(v) => verify(v)"
/>7. Error state
Set error to show a red border and trigger a subtle shake animation — ideal for "wrong code" feedback.
<VxOtpInput v-model="code" :error="isInvalid" />8. Success state
<VxOtpInput v-model="code" success />9. Sizes & shapes
Three sizes (small / default / large) and three shapes (rounded / square / underline).
<VxOtpInput size="small" :length="4" />
<VxOtpInput size="large" :length="4" />
<VxOtpInput shape="rounded" />
<VxOtpInput shape="square" />
<VxOtpInput shape="underline" />
<VxOtpInput shape="connected" /> <!-- slots share borders, no gap -->10. Loading state
Pass loading to show a shimmer animation and lock input — handy while verifying a code server-side.
<VxOtpInput v-model="code" :loading="verifying" />11. Imperative API (ref)
focus() moves to the first empty slot; clear() resets and focuses the first slot.
<script setup lang="ts">
import { ref } from 'vue'
const otpRef = ref()
</script>
<template>
<VxOtpInput ref="otpRef" v-model="code" />
<button @click="otpRef.focus()">Focus</button>
<button @click="otpRef.clear()">Clear</button>
</template>12. Full verify flow
End-to-end: user types → complete fires → we verify → show success or error shake. Code 123456 passes, anything else fails.
123456)<script setup lang="ts">
import { ref } from 'vue'
const code = ref('')
const state = ref<'idle' | 'loading' | 'error' | 'success'>('idle')
async function onComplete(v: string) {
state.value = 'loading'
const ok = await verify(v)
state.value = ok ? 'success' : 'error'
}
</script>
<template>
<VxOtpInput
v-model="code"
:loading="state === 'loading'"
:error="state === 'error'"
:success="state === 'success'"
:separator="3"
auto-focus
@complete="onComplete"
/>
</template>13. Fully customizable — class-bindable
Every part (root, slot, separator) accepts a class binding. Combine with custom CSS for branded looks.
<VxOtpInput
v-model="code"
:length="6"
:separator="3"
root-class="neon-otp"
slot-class="neon-otp__slot"
separator-class="neon-otp__sep"
/>
<style>
.neon-otp {
padding: 14px 18px;
border-radius: 16px;
background: radial-gradient(120% 120% at 0% 0%, rgba(99, 102, 241, 0.18), transparent 50%), #0b0b12;
}
.neon-otp__slot {
background: rgba(17, 17, 27, 0.8) !important;
border-color: rgba(129, 140, 248, 0.3) !important;
color: #e0e7ff !important;
}
</style>Props
| Prop | Type | Default | Description |
|---|---|---|---|
modelValue | string | '' | Bound value (v-model) |
length | number | 6 | Number of slots |
type | 'number' | 'text' | 'number' | Input mode — numeric keyboard vs text |
allowedPattern | RegExp | null | null | Custom per-char validation regex |
mask | boolean | false | Hide entered characters (password-style) |
placeholder | string | '' | Single-char placeholder per empty slot |
autoFocus | boolean | false | Focus first empty slot on mount |
autoSubmit | boolean | true | Blur last slot after it's filled |
selectOnFocus | boolean | true | Select content on focus for easy overwrite |
size | 'small' | 'default' | 'large' | 'default' | Slot size |
shape | 'rounded' | 'square' | 'underline' | 'connected' | 'rounded' | Slot shape (connected joins slots into one pill) |
separator | number | 0 | Insert separator every N slots (0 = none) |
separatorChar | string | '-' | Character used as separator |
disabled | boolean | false | Lock input |
loading | boolean | false | Shimmer animation and input lock |
error | boolean | false | Red border + shake animation |
success | boolean | false | Green border + filled state |
rootClass / slotClass / separatorClass | string | string[] | object | '' | Class bindings for every DOM part |
Events
| Event | Payload | When |
|---|---|---|
update:modelValue | value: string | Value changed (any slot) |
change | value: string | Alias of update:modelValue |
complete | value: string | Every slot is filled |
Exposed methods (ref)
| Method | Description |
|---|---|
focus() | Focus the first empty slot (or last if all filled) |
clear() | Reset all slots and focus the first |
blur() | Blur all slots |
Keyboard
| Key | Action |
|---|---|
0–9 / char | Fills current slot, auto-advances |
Backspace | Clears current; on empty, moves to previous and clears it |
Delete | Clears current slot, stays |
← / → | Navigate between slots |
Home / End | Jump to first / last slot |
Cmd/Ctrl + V | Paste distributes across slots |