n-input-otp Component
The n-input-otp component is designed for entering one-time passwords (OTP) or verification codes. It provides individual input boxes for each character, with support for masking, custom rendering, and paste functionality.
When to Use
Use n-input-otp when you need to:
- Collect verification codes or OTPs
- Create PIN input fields
- Build authentication code entry forms
- Implement two-factor authentication UI
- Create any fixed-length code input
Basic Usage
Basic OTP Input
<template>
<n-input-otp
v-model:value="value"
@focus="onFocus"
@blur="onBlur"
@finish="onFinish"
@update:value="onUpdateValue"
/>
</template>
<script setup>
import { ref } from 'vue'
const value = ref([])
const index) => console.log('Focus:', index)
const index) => console.log('Blur:', index)
const => console.log('Finished:', value.join(''))
const meta) => console.log('Update:', value, meta)
</script>
Custom Length
<template>
<n-input-otp v-model:value="value" :length="4" />
</template>
With Mask (Password Mode)
<template>
<n-input-otp v-model:value="value" mask />
</template>
Different Sizes
<template>
<n-space vertical>
<n-input-otp v-model:value="value" size="small" />
<n-input-otp v-model:value="value" />
<n-input-otp v-model:value="value" size="large" />
</n-space>
</template>
API Reference
Props
| Name | Type | Default | Description |
|---|---|---|---|
value / v-model:value |
string[] |
undefined |
Value of the component (in controlled mode). |
default-value |
string[] |
[] |
Default value. |
length |
number |
6 |
Number of characters to initiate. |
disabled |
boolean |
false |
Whether the component is disabled. |
readonly |
boolean |
false |
Whether the component is readonly. |
mask |
boolean |
false |
Whether to enable password mode. |
placeholder |
string |
'' |
Input placeholder. |
size |
'small' | 'medium' | 'large' |
'medium' |
Size of the component. |
status |
'success' | 'warning' | 'error' |
undefined |
The validation status. |
block |
boolean |
false |
Whether to fit its width to its parent's width. |
gap |
string | number |
undefined |
Gap between different inputs. |
allow-input |
(char: string, index: number, currentValue: string[]) => boolean |
undefined |
Check the incoming value, if it returns false, input will not be accepted. |
Events
| Name | Parameters | Description |
|---|---|---|
update:value |
(value: string[], meta: { diff, index, source }) => void |
Callback fired when user inputs value. |
focus |
(event: FocusEvent, index: number) => void |
Callback fired when focus moves to the component. |
blur |
(event: FocusEvent, index: number) => void |
Callback fired when focus leaves the component. |
finish |
(value: string[]) => void |
Callback fired when all inputs are filled. |
Slots
| Name | Parameters | Description |
|---|---|---|
default |
{ index, ...inputProps } |
Custom input rendering. |
Methods
| Name | Type | Description |
|---|---|---|
focusOnChar |
(charIndex: number) => void |
Focus on a specific input box. |
Common Patterns
Limit to Numbers Only
<template>
<n-input-otp :allow-input="onlyAllowNumber" />
</template>
<script setup>
const => {
return /^\d$/.test(char)
}
</script>
Limit to Letters Only
<template>
<n-input-otp :allow-input="onlyAllowEnglish" />
</template>
<script setup>
const => {
return /^[a-zA-Z]$/.test(char)
}
</script>
Custom Rendering with Separators
<template>
<n-input-otp v-model:value="value" gap="0">
<template #default="{ index, ...inputProps }">
<n-input
v-bind="inputProps"
:style="index === 0 ? '' : 'margin-left: 6px;'"
/>
<span v-if="index === 1" style="padding-left: 6px">-</span>
<span v-if="index === 3" style="padding-left: 6px">-</span>
</template>
</n-input-otp>
</template>
Block Width
<template>
<n-input-otp v-model:value="value" block />
</template>
Use with Form Validation
<template>
<n-form ref="formRef" :model="formValue" :rules="rules">
<n-form-item path="passcode" label="Pass Code">
<n-input-otp v-model:value="formValue.passcode" />
</n-form-item>
<n-form-item>
<n-button @click="handleValidateClick">Validate</n-button>
</n-form-item>
</n-form>
</template>
<script setup>
import { ref } from 'vue'
const formRef = ref(null)
const formValue = ref({
passcode: []
})
const rules = {
passcode: {
required: true,
validator: (rule, value) => {
if (value.length !== 6 || value.some(v => !v)) {
return new Error('Please enter complete 6-digit code')
}
return true
}
}
}
const handleValidateClick = () => {
formRef.value?.validate()
}
</script>
Different Status States
<template>
<n-space vertical>
<n-input-otp :default-value="'123456'.split('')" readonly />
<n-input-otp :default-value="'123456'.split('')" disabled />
<n-input-otp :default-value="'123456'.split('')" status="warning" />
<n-input-otp :default-value="'123456'.split('')" status="error" />
<n-input-otp :default-value="'123456'.split('')" status="success" />
</n-space>
</template>
Auto-submit on Complete
<template>
<n-input-otp v-model:value="value" @finish="handleSubmit" />
</template>
<script setup>
import { ref } from 'vue'
const value = ref([])
const handleSubmit = (code) => {
const otp = code.join('')
console.log('Submitting OTP:', otp)
}
</script>
Best Practices
Use
finishevent for auto-submission: Automatically submit when all characters are entered for better UX.Set
allow-inputfor format restriction: Restrict input to numbers or letters based on your OTP format.Use
maskfor sensitive codes: Enable mask mode for PINs or security codes.Provide
statusfeedback: Show success/error status after validation.Use
blockfor full-width layouts: Make the component fill its container for mobile-friendly designs.Handle paste events gracefully: The component supports paste by default; ensure your backend handles the complete code.