Skip to content

UIInputOtp

UIInputOtp

A specialized input component designed for entering one-time passwords (OTP) with support for auto-focus, validation, and customization.

<template>
<UIInputOtp
v-model="otp"
:length="6"
:validateChar="validateNumeric"
placeholder="Enter OTP"
@complete="handleComplete"
/>
</template>
<script setup lang="ts">
const otp = ref('')
const validateNumeric = (char: string) => {
return /^\d$/.test(char)
}
const handleComplete = (value: string) => {
console.log('OTP entered:', value)
}
</script>

Props

  • modelValue (string): OTP value
  • length (number): Number of OTP digits
  • validateChar (function): Character validation function
  • placeholder (string): Input placeholder
  • disabled (boolean): Disable input
  • loading (boolean): Loading state
  • type (‘text’ | ‘number’ | ‘password’): Input type
  • size (‘sm’ | ‘md’ | ‘lg’): Input size
  • autoFocus (boolean): Auto focus first input
  • secure (boolean): Mask input as password
  • inputClass (string): Input CSS class
  • error (boolean): Error state

Events

  • update:modelValue: Value updated
  • complete: OTP entry completed
  • change: Value changed
  • focus: Input focused
  • blur: Input blurred
  • paste: Value pasted

Slots

  • input: Custom input render
  • separator: Custom separator content
  • prefix: Input prefix content
  • suffix: Input suffix content

Usage Examples

  1. Basic OTP Input:
<template>
<UIInputOtp
v-model="otp"
:length="4"
/>
</template>
<script setup>
const otp = ref('')
</script>
  1. Phone Verification:
<template>
<div class="space-y-4">
<div class="text-center">
<h3 class="text-lg font-medium">
Verify Your Phone
</h3>
<p class="text-gray-600">
Enter the 6-digit code sent to
<span class="font-medium">
{{ formatPhone(phone) }}
</span>
</p>
</div>
<UIInputOtp
v-model="verificationCode"
:length="6"
:validateChar="validateNumeric"
:loading="verifying"
:error="!!error"
placeholder="000000"
class="justify-center"
@complete="handleVerification"
/>
<div
v-if="error"
class="text-sm text-error-500 text-center"
>
{{ error }}
</div>
<div class="text-center">
<button
class="text-sm text-primary-600 hover:text-primary-500"
:disabled="resendDisabled"
@click="resendCode"
>
{{ resendText }}
</button>
</div>
</div>
</template>
<script setup>
const verificationCode = ref('')
const verifying = ref(false)
const error = ref('')
const resendTimer = ref(30)
const resendDisabled = computed(() => resendTimer.value > 0)
const resendText = computed(() => {
return resendDisabled.value
? `Resend code in ${resendTimer.value}s`
: 'Resend code'
})
const validateNumeric = (char: string) => {
return /^\d$/.test(char)
}
const handleVerification = async (code: string) => {
verifying.value = true
error.value = ''
try {
await verifyPhone(code)
// Handle success
} catch (err) {
error.value = err.message
verificationCode.value = ''
} finally {
verifying.value = false
}
}
const startResendTimer = () => {
resendTimer.value = 30
const interval = setInterval(() => {
if (resendTimer.value > 0) {
resendTimer.value--
} else {
clearInterval(interval)
}
}, 1000)
}
const resendCode = async () => {
if (resendDisabled.value) return
try {
await sendVerificationCode(phone)
startResendTimer()
} catch (err) {
error.value = err.message
}
}
onMounted(() => {
startResendTimer()
})
</script>
  1. Two-Factor Authentication:
<template>
<div class="space-y-6">
<div class="space-y-2">
<h3 class="text-lg font-medium">
Two-Factor Authentication
</h3>
<p class="text-gray-600">
Enter the authentication code from your authenticator app
</p>
</div>
<UIInputOtp
v-model="authCode"
:length="6"
:validateChar="validateNumeric"
:secure="true"
:size="'lg'"
placeholder="••••••"
class="justify-center"
@complete="handleAuthentication"
>
<template #separator>
<span class="text-gray-300 mx-2">
-
</span>
</template>
</UIInputOtp>
<div class="flex items-center justify-between">
<button
class="text-sm text-gray-600 hover:text-gray-900"
@click="useBackupCode"
>
Use backup code
</button>
<UIButton
type="primary"
:loading="authenticating"
:disabled="authCode.length !== 6"
@click="handleAuthentication"
>
Verify
</UIButton>
</div>
</div>
</template>
<script setup>
const authCode = ref('')
const authenticating = ref(false)
const handleAuthentication = async () => {
if (authCode.value.length !== 6) return
authenticating.value = true
try {
await verify2FA(authCode.value)
// Handle success
} catch (error) {
// Handle error
} finally {
authenticating.value = false
}
}
</script>

Best Practices

  1. User Experience:

    • Auto focus
    • Clear feedback
    • Error handling
    • Mobile optimization
  2. Performance:

    • Efficient updates
    • Minimal reflows
    • Memory management
    • Input optimization
  3. Accessibility:

    • ARIA labels
    • Keyboard navigation
    • Screen reader support
    • Focus management
  4. Security:

    • Input validation
    • Secure input
    • Rate limiting
    • Error handling

Common Use Cases

  1. Email Verification:
<template>
<div class="space-y-4">
<UIInputOtp
v-model="emailCode"
:length="6"
:validateChar="validateNumeric"
:error="!!error"
placeholder="Enter code"
@complete="verifyEmail"
/>
<div class="text-center text-sm">
<span class="text-gray-600">
Didn't receive the code?
</span>
<button
class="text-primary-600 hover:text-primary-500 ml-1"
@click="resendEmail"
>
Resend
</button>
</div>
</div>
</template>
  1. PIN Entry:
<template>
<UIInputOtp
v-model="pin"
:length="4"
:validateChar="validateNumeric"
:secure="true"
type="password"
placeholder="••••"
@complete="handlePinEntry"
>
<template #input="{ index, active }">
<div
class="w-12 h-12 rounded-full border-2 flex items-center justify-center"
:class="{
'border-primary-500': active,
'border-gray-200': !active
}"
>
</div>
</template>
</UIInputOtp>
</template>
  1. Confirmation Code:
<template>
<div class="space-y-4">
<UIInputOtp
v-model="confirmationCode"
:length="8"
:size="'lg'"
class="justify-center"
>
<template #separator="{ index }">
<span
v-if="(index + 1) % 4 === 0"
class="mx-4 text-gray-300"
>
-
</span>
</template>
</UIInputOtp>
<div class="flex justify-end space-x-2">
<UIButton
variant="outline"
@click="cancel"
>
Cancel
</UIButton>
<UIButton
type="primary"
:disabled="confirmationCode.length !== 8"
@click="confirm"
>
Confirm
</UIButton>
</div>
</div>
</template>

Component Composition

The UIInputOtp component works well with:

  • UIButton for actions
  • UISpinner for loading states
  • UITooltip for help text
  • UIIcon for status indicators
  • UINotification for feedback
  • UIModal for verification flows
  • UIForm for validation
  • UIAlert for error messages