Vue One-Time Passcode Input

0.3.2 · active · verified Sun Apr 19

vue-input-otp is an accessible and unstyled Vue 3 component designed for capturing one-time passcodes (OTPs). It provides a flexible slot-based API, allowing developers complete control over the visual presentation of each digit input, rather than enforcing a specific UI. The library works by rendering an invisible native input that handles the underlying logic, while exposing slots for custom rendering. The current stable version is 0.3.2, with minor and patch releases occurring as needed. Its key differentiator is its 'bring-your-own-style' approach, offering maximum customization and robust accessibility features, including password manager support and precise control over virtual keyboard behavior on mobile devices. It aims to solve the lack of a native HTML OTP input by providing a robust, extensible solution for Vue applications.

Common errors

Warnings

Install

Imports

Quickstart

This quickstart demonstrates how to integrate `OTPInput` into a Vue 3 application, using `v-model` for two-way data binding and leveraging the slot props to render custom input fields, including a 'fake dash' separator, and local state management with `ref`.

<script setup lang="ts">
import { ref } from 'vue';
import { OTPInput } from 'vue-input-otp';

const input = ref('123456');

// Example Slot component (simplified for brevity, assume 'cn' utility and 'isActive' prop are defined elsewhere)
// In a real app, you'd likely define this in a separate file, e.g., 'Slot.vue'
const Slot = {
  props: ['char', 'isActive'],
  template: `
    <div
      :class="[
        '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>
      <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>
  `
};
</script>

<template>
  <form class="flex flex-col gap-4 p-4">
    <label for="otp-input" class="text-lg font-medium">Enter OTP:</label>
    <OTPInput
      id="otp-input"
      v-slot="{ slots }"
      v-model="input"
      :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>
      <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>
    <p class="mt-2 text-sm text-gray-600">Current value: {{ input }}</p>
  </form>
</template>

view raw JSON →