{"record":{"id":"0a04658d26a7ce0c","repo":"vuetifyjs/vuetify","slug":"vuetify-votpfield-must-be-used-inside-votpinput","errorCode":null,"errorMessage":"[Vuetify] VOtpField must be used inside VOtpInput","messagePattern":"\\[Vuetify\\] VOtpField must be used inside VOtpInput","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/vuetify/src/components/VOtpInput/VOtpField.tsx","lineNumber":27,"sourceCode":"import { VOtpInputSymbol } from './shared'\n\nexport const makeVOtpFieldProps = propsFactory({\n  index: {\n    type: Number,\n    required: true,\n  },\n}, 'VOtpField')\n\nexport const VOtpField = genericComponent()({\n  name: 'VOtpField',\n\n  props: makeVOtpFieldProps(),\n\n  setup (props) {\n    const otpInput = inject(VOtpInputSymbol)\n\n    if (!otpInput) {\n      throw new Error('[Vuetify] VOtpField must be used inside VOtpInput')\n    }\n\n    const slot = computed(() => otpInput.otpSlots.value[props.index])\n\n    const isHighlighted = computed(() =>\n      otpInput.isFocused.value && otpInput.focusAll.value && !slot.value?.isActive\n    )\n\n    useRender(() => {\n      if (!slot.value) return createCommentVNode()\n\n      return (\n        <VField\n          data-otp-index={ props.index }\n          focused={ slot.value.isActive || isHighlighted.value }\n          class={{ 'v-otp-input__field--highlighted': isHighlighted.value }}\n          onMousedown={ (e: MouseEvent) => e.preventDefault() }\n          onClick={ () => otpInput.focusAt(props.index) }","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/vuetifyjs/vuetify/blob/8d153908dffb7592eb389ddbfbab955a0ccc977f/packages/vuetify/src/components/VOtpInput/VOtpField.tsx#L9-L45","documentation":"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.","triggerScenarios":"Rendering <v-otp-field> as a standalone component; placing it outside <v-otp-input>; composing a custom OTP layout that loses the parent.","commonSituations":"Trying to lay out OTP fields manually with <v-otp-field>; copy-pasting a field out of an input; testing the field alone.","solutions":["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).","If you need a custom layout, keep all fields inside one <v-otp-input> provide tree.","In tests, wrap the field in <v-otp-input>."],"exampleFix":"// before\n<v-otp-field />\n\n// after\n<v-otp-input />\n<!-- let v-otp-input render its fields internally -->","handlingStrategy":"type-guard","validationCode":"import { inject } from 'vue'\nimport { VOtpInputSymbol } from 'vuetify'\nconst otp = inject(VOtpInputSymbol, null)\nif (!otp) {\n  // not inside <v-otp-input>: render a plain text field instead\n}","typeGuard":"import { inject } from 'vue'\nimport { VOtpInputSymbol } from 'vuetify'\nexport function isInsideOtpInput (): boolean {\n  return inject(VOtpInputSymbol, null) != null\n}","tryCatchPattern":null,"preventionTips":["Render <v-otp-field> only inside <v-otp-input>.","Prefer configuring <v-otp-input> over assembling fields manually.","Wrap fields in <v-otp-input> for tests."],"tags":["injection","otp-input","components"],"backgroundTag":null,"analyzedSha":"8d153908dffb7592eb389ddbfbab955a0ccc977f","analyzedAt":"2026-08-12T21:54:20.596Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}