Skip to main content
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One
Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

Otp

Headless one-time-password and verification-code boxes with auto-advance, paste distribution, and an observational complete event.

Edit this page
Report a Bug
Open issues
View on GitHub
Copy Markdown

PreviewRenders elementIntermediateOct 13, 2026

Usage

Otp renders a group of single-character boxes. Items expose fill state via data attributes for CSS-only styling.

Code: —

Theme
Mode
Palettes
Accessibility
<script setup lang="ts">
  import { Otp } from '@vuetify/v0'
  import { shallowRef } from 'vue'

  const code = shallowRef('')
</script>

<template>
  <div class="flex flex-col items-center gap-4">
    <Otp.Root v-slot="{ items }" v-model="code" class="flex gap-2" :length="6">
      <Otp.Item
        v-for="item in items"
        :key="item.index"
        class="w-10 h-12 text-center border border-divider rounded text-lg outline-none [&[data-state=filled]]:border-primary"
        :index="item.index"
      />
    </Otp.Root>

    <p class="text-sm text-on-surface-variant">
      Code: {{ code || '—' }}
    </p>
  </div>
</template>

Anatomy

vue
<script setup lang="ts">
  import { Otp } from '@vuetify/v0'
</script>

<template>
  <Otp.Root>
    <Otp.Item />

    <Otp.HiddenInput />
  </Otp.Root>
</template>

Examples

Two-factor sign-in form

A second-factor step that posts the code through a real <form> the moment the last box fills. Setting name="code" on Otp.Root auto-renders Otp.HiddenInput, so the joined value travels with the form like any native field — the summary panel reads it back with new FormData(form) to prove what the browser would send. There is no submit button: @complete calls requestSubmit(), which runs the Form submit pipeline (including any registered field validation) before the @submit handler fires.

Form runs in renderless mode so the example owns the <form> element and can hand it to FormData; Form’s @submit resolves after its async validation pass, by which point the hidden input carries the full code.

Reach for this shape when the code is the whole step and nothing else needs confirming. If the server can reject the code and the field must clear and lock while the request is in flight, build on createOtp and its decisional onComplete instead — @complete is observational and cannot veto a value. Related: Input, Form.

FileRole
useTwoFactor.tsOwns the code, the submitted payload, and reset
TwoFactorForm.vueRenders Otp inside a Form and auto-submits on @complete
two-factor.vueWires the composable to the form and shows the posted payload

Enter the 6-digit code from your authenticator app. The form submits itself when the last box fills.

Theme
Mode
Palettes
Accessibility

Recipes

Completion

@complete fires when the joined value becomes a new complete string. It is observational — return values are ignored, same as Form @submit and Portal @close. For reject-and-retry (clear on invalid code, lock while a request is in flight), use createOtp and its onComplete option.

vue
<script setup lang="ts">
  import { Otp } from '@vuetify/v0'
  import { shallowRef } from 'vue'

  const code = shallowRef('')

  function verify (value: string) {
    void fetch('/verify', { method: 'POST', body: value })
  }
</script>

<template>
  <Otp.Root
    v-slot="{ items }"
    v-model="code"
    :length="6"
    @complete="verify"
  >
    <Otp.Item
      v-for="item in items"
      :key="item.index"
      :index="item.index"
    />
  </Otp.Root>
</template>

Accessibility

Otp.Root is a role="group" with a locale-driven default accessible name. Each Item is a single-character text input with autocomplete="one-time-code" so password managers and SMS autofill can target the field.

Keyboard

KeyAction
CharacterWrites the character if it matches pattern at min(focused index, current length) on the compact string and advances. A keystroke in a later empty box lands in the first empty slot, not the focused box
Backspace (empty box)Clears the previous box and moves focus back
Backspace (filled box)Truncates the compact value from this index onward — this box and every box after it clear
ArrowLeft / ArrowRightMove focus to the previous / next box (RTL-aware)
Home / EndMove focus to the first / last box
PasteDistributes clipboard text from min(focused index, current length) on the compact string. A paste into a later empty box fills from the first empty slot, not the focused box

ARIA

Root:

AttributeValue
rolegroup
aria-labelLocale default, or ariaLabel when ariaLabelledby is unset
aria-labelledbyWhen ariaLabelledby is set (replaces aria-label)
aria-describedbyWhen ariaDescribedby is set
aria-disabledtrue when disabled

Item:

AttributeValue
aria-labelLocale default, e.g. “Digit 1 of 6”
aria-describedbySame id as Root when ariaDescribedby is set
autocompleteone-time-code
inputmodenumeric when pattern is 'numeric', otherwise text

Data Attributes

Root:

AttributeDescription
data-disabledPresent when disabled
data-readonlyPresent when readonly
data-completePresent when the value is complete and pattern-valid

Item:

AttributeValuesDescription
data-statefilled | emptyWhether this box has a character
data-disabledpresent/absentInherited from root
data-readonlypresent/absentInherited from root

FAQ

Discord
Need help? Join our community for support and discussions ↗

API Reference

The following API details are for all variations of the Otp component.
Was this page helpful?

© 2016-1970 Vuetify, LLC
Services
Ctrl+/