OTP Input for Vue
https://github.com/wobsoriano/vue-input-otp/assets/13049130/c5080f41-f411-4d38-aa57-d04d90c832c3
One-time passcode Input. Accessible & unstyled. Based on the React version by guilhermerodz.
Installation
npm install vue-input-otp
Usage
<script setup lang="ts">
import { OTPInput } from 'vue-input-otp'
import Slot from './Slot.vue'
const input = ref('123456')
</script>
<template>
<form>
<OTPInput v-slot="{ slots }" v-model="input" :maxlength="6">
<!-- slots -->
</OTPInput>
</form>
</template>
Default example
The example below uses tailwindcss
, shadcn-vue
, tailwind-merge
and clsx
:
<script setup lang="ts">
import { OTPInput } from 'vue-input-otp'
import Slot from './Slot.vue'
</script>
<template>
<OTPInput
v-slot="{ slots }"
:maxlength="6"
container-class="group flex items-center has-[:disabled]:opacity-30"
>
<div class="flex">
<Slot v-for="(slot, idx) in slots.slice(0, 3)" v-bind="slot" :key="idx" />
</div>
<!-- Fake Dash. Inspired by Stripe's MFA input. -->
<div class="flex w-10 justify-center items-center">
<div class="w-3 h-1 rounded-full bg-border" />
</div>
<div class="flex">
<Slot v-for="(slot, idx) in slots.slice(3)" v-bind="slot" :key="idx" />
</div>
</OTPInput>
</template>
<script setup lang="ts">
import type { SlotProps } from 'vue-input-otp'
import { cn } from '$lib/utils'
defineProps<SlotProps>()
</script>
<template>
<div
:class="cn(
'relative w-10 h-14 text-[2rem]',
'flex items-center justify-center',
'transition-all duration-300',
'border-border border-y border-r first:border-l first:rounded-l-md last:rounded-r-md',
'group-hover:border-accent-foreground/20 group-focus-within:border-accent-foreground/20',
'outline outline-0 outline-accent-foreground/20',
{ 'outline-4 outline-accent-foreground': isActive },
)"
>
<div v-if="char !== null">
{{ char }}
</div>
<!-- Emulate a Fake Caret -->
<div v-if="char === null && isActive" class="absolute pointer-events-none inset-0 flex items-center justify-center animate-caret-blink">
<div class="w-px h-8 bg-white" />
</div>
</div>
</template>
// tailwind.config.ts for the blinking caret animation.
// Small utility to merge class names.
import { clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
import type { ClassValue } from 'clsx'
const config = {
theme: {
extend: {
keyframes: {
'caret-blink': {
'0%,70%,100%': { opacity: '1' },
'20%,50%': { opacity: '0' },
},
},
animation: {
'caret-blink': 'caret-blink 1.2s ease-out infinite',
},
},
},
}
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
How it works
There's currently no native OTP/2FA/MFA input in HTML, which means people are either going with 1. a simple input design or 2. custom designs like this one. This library works by rendering an invisible input as a sibling of the slots, contained by a relative
ly positioned parent (the container root called OTPInput).
API Reference
OTPInput
The root container. Define settings for the input via props. Then, pass in child elements to create the slots.
Props
Name | Description | Type | Values | Default |
---|
maxlength | The number of slots. | number | - | - |
containerClass | The class for the root container. | string | - | - |
textAlign | Where is the text located within the input. Affects click-holding or long-press behavior. | string | left , right , center | center |
inputmode | Virtual keyboard appearance on mobile. | string | numeric , text | numeric |
pushPasswordManagerStrategy | Detect Password Managers and shift their badges to the right side, outside the input. | string | increase-width , none | increase-width |
Slots
Name | Description | Props |
---|
default | The slots to be rendered. | slots: SlotProps[], isFocused: boolean, isHovering: boolean |
Events
Name | Description | Parameters |
---|
complete | Emitted when the input is complete. | value: string |
Examples
Automatic form submission on OTP completion
<script setup>
import { OTPInput } from 'vue-input-otp'
import { ref } from 'vue'
const formRef = ref()
const buttonRef = ref()
function onComplete() {
// ... automatically submit the form
formRef.value.submit()
// ... or focus the button like as you wish
buttonRef.value.focus()
}
</script>
<template>
<form ref="formRef">
<OTPInput @complete="onComplete" />
<button ref="buttonRef">
Submit
</button>
</form>
</template>
Automatically focus the input when the page loads
<script setup>
import { OTPInput } from 'vue-input-otp'
</script>
<template>
<form>
<!-- Pro tip: accepts all common HTML input props... -->
<OTPInput auto-focus />
</form>
</template>
Caveats
See list of caveats in the original implementation here.
License
MIT