Skip to content

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.

Value: —
vue
<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.

4-digit PIN
8-digit backup code
vue
<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.

3-3
2-2-2 with dot
vue
<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.

Actual value: —
vue
<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).

Any character
Uppercase A–Z only
vue
<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.

Status: Waiting for input…
vue
<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.

vue
<VxOtpInput v-model="code" :error="isInvalid" />

8. Success state ​

✓ Verified
vue
<VxOtpInput v-model="code" success />

9. Sizes & shapes ​

Three sizes (small / default / large) and three shapes (rounded / square / underline).

Sizes
Shapes
vue
<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.

Verifying…
vue
<VxOtpInput v-model="code" :loading="verifying" />

11. Imperative API (ref) ​

focus() moves to the first empty slot; clear() resets and focuses the first slot.

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

Enter the 6-digit code (hint: 123456)
vue
<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.

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

PropTypeDefaultDescription
modelValuestring''Bound value (v-model)
lengthnumber6Number of slots
type'number' | 'text''number'Input mode — numeric keyboard vs text
allowedPatternRegExp | nullnullCustom per-char validation regex
maskbooleanfalseHide entered characters (password-style)
placeholderstring''Single-char placeholder per empty slot
autoFocusbooleanfalseFocus first empty slot on mount
autoSubmitbooleantrueBlur last slot after it's filled
selectOnFocusbooleantrueSelect 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)
separatornumber0Insert separator every N slots (0 = none)
separatorCharstring'-'Character used as separator
disabledbooleanfalseLock input
loadingbooleanfalseShimmer animation and input lock
errorbooleanfalseRed border + shake animation
successbooleanfalseGreen border + filled state
rootClass / slotClass / separatorClassstring | string[] | object''Class bindings for every DOM part

Events ​

EventPayloadWhen
update:modelValuevalue: stringValue changed (any slot)
changevalue: stringAlias of update:modelValue
completevalue: stringEvery slot is filled

Exposed methods (ref) ​

MethodDescription
focus()Focus the first empty slot (or last if all filled)
clear()Reset all slots and focus the first
blur()Blur all slots

Keyboard ​

KeyAction
0–9 / charFills current slot, auto-advances
BackspaceClears current; on empty, moves to previous and clears it
DeleteClears current slot, stays
← / →Navigate between slots
Home / EndJump to first / last slot
Cmd/Ctrl + VPaste distributes across slots