vuetifyjs/vuetify · error · Error

[Vuetify] VOtpField must be used inside VOtpInput

Error message

[Vuetify] VOtpField must be used inside VOtpInput

What it means

VOtpField injects VOtpInputSymbol, provided only by <v-otp-input>. It throws when <v-otp-field> is rendered outside <v-otp-input>, because each field reads its slot, focus, and highlight state from the parent OTP input.

Source

Thrown at packages/vuetify/src/components/VOtpInput/VOtpField.tsx:27

import { VOtpInputSymbol } from './shared'

export const makeVOtpFieldProps = propsFactory({
  index: {
    type: Number,
    required: true,
  },
}, 'VOtpField')

export const VOtpField = genericComponent()({
  name: 'VOtpField',

  props: makeVOtpFieldProps(),

  setup (props) {
    const otpInput = inject(VOtpInputSymbol)

    if (!otpInput) {
      throw new Error('[Vuetify] VOtpField must be used inside VOtpInput')
    }

    const slot = computed(() => otpInput.otpSlots.value[props.index])

    const isHighlighted = computed(() =>
      otpInput.isFocused.value && otpInput.focusAll.value && !slot.value?.isActive
    )

    useRender(() => {
      if (!slot.value) return createCommentVNode()

      return (
        <VField
          data-otp-index={ props.index }
          focused={ slot.value.isActive || isHighlighted.value }
          class={{ 'v-otp-input__field--highlighted': isHighlighted.value }}
          onMousedown={ (e: MouseEvent) => e.preventDefault() }
          onClick={ () => otpInput.focusAt(props.index) }

View on GitHub (pinned to 8d153908df)

Solutions

  1. Use <v-otp-field> only as a child of <v-otp-input> (normally you just configure <v-otp-input> and let it render its fields).
  2. If you need a custom layout, keep all fields inside one <v-otp-input> provide tree.
  3. In tests, wrap the field in <v-otp-input>.

Example fix

// before
<v-otp-field />

// after
<v-otp-input />
<!-- let v-otp-input render its fields internally -->
Defensive patterns

Strategy: type-guard

Validate before calling

import { inject } from 'vue'
import { VOtpInputSymbol } from 'vuetify'
const otp = inject(VOtpInputSymbol, null)
if (!otp) {
  // not inside <v-otp-input>: render a plain text field instead
}

Type guard

import { inject } from 'vue'
import { VOtpInputSymbol } from 'vuetify'
export function isInsideOtpInput (): boolean {
  return inject(VOtpInputSymbol, null) != null
}

Prevention

When it happens

Trigger: Rendering <v-otp-field> as a standalone component; placing it outside <v-otp-input>; composing a custom OTP layout that loses the parent.

Common situations: Trying to lay out OTP fields manually with <v-otp-field>; copy-pasting a field out of an input; testing the field alone.

Related errors


AI-assisted analysis of vuetifyjs/vuetify@8d153908df (2026-08-12). Data as JSON: /api/errors/0a04658d26a7ce0c. Report an issue: GitHub.