vue-input-otp

https://github.com/wobsoriano/vue-input-otp/assets/13049130/c5080f41-f411-4d38-aa57-d04d90c832c3
2,132,109 90
Install
npm install vue-input-otp
Latest Version:0.4.0
Vue:3
License:MIT
Last Updated:Aug 13, 2026
Links: GitHub  ·  npm
Maintainer: wobsoriano

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>
import type { ClassValue } from 'clsx'
// tailwind.config.ts for the blinking caret animation.
// Small utility to merge class names.
import { clsx } from 'clsx'

import { twMerge } from 'tailwind-merge'

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 relatively 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. The shift is skipped when the extra 40px would not fit inside the nearest element constraining horizontal overflow. string increase-width, none increase-width
spellcheck Browsers otherwise underline a filled code as a spelling error. boolean - false
nonce Applied to the <style> tag this component injects, for a style-src Content Security Policy that requires nonces. string - -
Slots
Name Description Props
default The slots to be rendered. slots: SlotProps[], isFocused: boolean, isHovering: boolean

Each entry in slots is a SlotProps:

Name Description Type
char The character in this slot, or null when the slot is empty. string | null
placeholderChar The placeholder character for this slot. null on every slot as soon as the value is non-empty, so placeholders disappear the moment the user starts typing. string | null
isActive Whether the selection currently covers this slot. boolean
hasFakeCaret Whether to render the blinking caret in this slot. boolean
Events
Name Description Parameters
complete Emitted when the input is complete. value: string

Examples

Automatic form submission on OTP completion
<script setup>
import { ref } from 'vue'
import { OTPInput } from 'vue-input-otp'

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 autofocus />
  </form>
</template>

Caveats

See list of caveats in the original implementation here.

License

MIT

Related Packages

@bachdgvn/vue-otp-input

A fully customizable, one-time password input component for the web built with V...

202,248 125
vue3-otp-input-field

Customizable OTP input field component for Vue 3

3,050 0
atalevue

a vue module

1,819
@8bu/vue-otp-input

Simple & Flexible OTP Style Input

9,953

Version History

Version Released Vue License
0.4.0 ^3.2.0 MIT
0.3.2 ^3.2.0 MIT
0.3.1 ^3.2.0 MIT
0.3.0 ^3.2.0 MIT
0.2.2 ^3.2.0 MIT