OTP Fields
OTP fields are used to allow users to input a one-time password or a code. Usually used for 2FA (MFA) or authentication purposes.
Features
Section titled “Features”- Use
inputor any other element to create an OTP field and its slots. - Labels, descriptions, and error message displays are automatically linked to input and label elements with
aria-*attributes. - Validation support with native HTML constraint validation or Standard Schema validation.
- Support for
v-modelbinding. - Supports masking (hiding) the entered characters with a custom character.
- Supports prefixes (e.g.
F-{code}). - Supports custom length.
- Supports both numeric and alphanumeric OTPs.
- Comprehensively supports keyboard navigation.
- Auto management of focus during user interaction.
- Handles paste events.
- Auto submit on completion.
Keyboard Features
Section titled “Keyboard Features”| Key | Description |
|---|---|
| →ArrowRight | Moves the focus to the next OTP slot. |
| ←ArrowLeft | Moves the focus to the previous OTP slot. |
| ⌫Backspace | Clears the current OTP slot and moves the focus to the previous slot. |
| ⇥Tab | Moves the focus to the next OTP slot. |
| ↩Enter | Moves the focus to the next OTP slot. |
Anatomy
Section titled “Anatomy”Building an OTP Field Component
Section titled “Building an OTP Field Component”First, import the useOtpField composable and use it in your OTP field component.
The useOtpField composable returns binding objects for the elements shown in the anatomy. You will use v-bind to bind them to the corresponding DOM elements.
<template> <div class="otp-field"> <div class="otp-field__label" v-bind="labelProps">{{ label }}</div>
<div class="otp-field__control" v-bind="controlProps"> <OtpSlot v-for="slot in fieldSlots" v-bind="slot" class="otp-field__slot" /> </div>
<span v-bind="errorMessageProps" class="otp-field__error-message"> {{ errorMessage }} </span> </div></template>
<script setup lang="ts">import { useOtpField, type OtpFieldProps, OtpSlot } from '@formwerk/core';
const props = defineProps<OtpFieldProps>();
const { controlProps, labelProps, errorMessage, errorMessageProps, fieldSlots,} = useOtpField(props);</script>
<style scoped>.otp-field { display: flex; flex-direction: column; gap: 0.5rem;
--fw-border-color: #aaa; --fw-input-bg: #fff; --fw-text-color: #000; --fw-error-color: #ff0000; --fw-focus-color: #10b981; --fw-disabled-bg: #e4e4e7;}
.otp-field__label { font-size: 14px; font-weight: 400; color: var(--fw-text-color);}
.otp-field__control { display: flex; gap: 0.5rem;}
.otp-field__slot { height: 2.5rem; width: 2.5rem; border-radius: 0.375rem; border: 1px solid var(--fw-border-color); background-color: var(--fw-input-bg); align-content: center; text-align: center; font-size: 1.3rem; font-weight: 500; color: var(--fw-text-color); vertical-align: middle; &:focus { outline: 1px solid #059669; background-color: #f0fdf4; caret-color: transparent; }
&:disabled { background-color: var(--fw-disabled-bg); opacity: 0.5; }}
.otp-field__error-message { width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--fw-error-color); font-size: 0.875rem;}</style>For your convenience, Formwerk already implements an OTP Slot component that you can use in your OTP field directly, but you can still build your own with useOtpSlot.
Validation
Section titled “Validation”HTML Constraints
Section titled “HTML Constraints”You can use the following properties to validate the OTP field with native HTML constraint validation:
| Name | Type | Description |
|---|---|---|
required | boolean | Whether the text field is required. |
<script setup lang="ts">import OtpField from './OtpField.vue';</script>
<template> <OtpField label="Your code" required /></template>Standard Schema
Section titled “Standard Schema”useOtpField also supports Standard Schema validation through the schema prop. This includes multiple providers like Zod, Valibot, Arktype, and more.
<script setup lang="ts">import OtpField from './OtpField.vue';import { z } from 'zod';
const schema = z.string().length(6);</script>
<template> <OtpField label="Your code" :schema="schema" /></template>Mixed Validation
Section titled “Mixed Validation”All OTP fields created with Formwerk support mixed validation, which means you can use both HTML constraints and Standard Schema validation to validate the field, and they work seamlessly together.
Note that HTML constraints are validated first, so any errors from the HTML constraints will be displayed first. Then, once all HTML constraints are satisfied, the Standard Schema is validated.
<script setup lang="ts">import OtpField from './OtpField.vue';import { z } from 'zod';
const schema = z.string().length(6).startsWith('abc');</script>
<template> <OtpField label="Your code" :schema="schema" required /></template>This makes schemas lighter; however, we recommend sticking to one or the other per form for maintainability.
If you need to disable the native validation, you can do so by setting the disableHtmlValidation prop to true.
Disabled
Section titled “Disabled”Use disabled to mark fields as non-interactive. Disabled fields are not validated and are not submitted.
If you need to prevent the user from interacting with the field while still allowing it to submit, consider using readonly instead.
<script setup lang="ts">import OtpField from './OtpField.vue';</script>
<template> <OtpField label="Disabled Code" disabled value="123456" /></template>Readonly
Section titled “Readonly”Use readonly to mark fields as non-editable. Readonly fields are still validated and submitted.
<script setup lang="ts">import OtpField from './OtpField.vue';</script>
<template> <OtpField label="Readonly Code" readonly value="123456" /></template>Accepting Specific Characters
Section titled “Accepting Specific Characters”The OTP field accepts a accept prop to specify the type of characters that can be entered.
You can have one of the following values:
| Value | Description |
|---|---|
alphanumeric | Alphanumeric characters are accepted (i.e: English letters and numbers), this is the default value. |
numeric | Only numeric characters are accepted. |
all | All characters are accepted. |
<script setup lang="ts">import OtpField from './OtpField.vue';</script>
<template> <OtpField label="Numeric Code" accept="numeric" /></template>Masking Characters
Section titled “Masking Characters”The OTP field accepts a mask prop to specify the character to use for masking the entered characters, your model and submitted values be the actual values. The mask prop can either be a boolean which will apply a masked default, or a character that will be used to mask the entered value.
<script setup lang="ts">import OtpField from './OtpField.vue';</script>
<template> <OtpField label="Secret Code" mask />
<OtpField label="Custom mask" mask="*" /></template>Prefix
Section titled “Prefix”OTP fields can have a prefix, prefixes cannot be changed or edited by the user.
<script setup lang="ts">import OtpField from './OtpField.vue';</script>
<template> <OtpField label="Prefixed Code" prefix="F-" /></template>Custom Length
Section titled “Custom Length”OTP fields accept a length prop to specify the number of OTP slots. By default the length is 6 without a prefix, and with a prefix it will be 4.
<script setup lang="ts">import OtpField from './OtpField.vue';</script>
<template> <OtpField label="Custom Length" length="5" /></template>onCompleted Handler
Section titled “onCompleted Handler”The OTP field accepts an onCompleted handler to be notified when the user has filled all the OTP slots with valid characters.
<script setup lang="ts">import OtpField from './OtpField.vue';
function onCompleted(value: string) { alert(`Code completed: ${value}`);}</script>
<template> <OtpField label="Enter Code" @completed="onCompleted" /></template>At this time, OTP fields do not support RTL (right-to-left) text direction. This is mainly because we want to get more feedback on this, from personal experience OTP codes are still LTR even in RTL web apps.
Feel free to open an issue on GitHub if you have any feedback on this.
useOtpField
Section titled “useOtpField”These are the properties that can be passed to the useOtpField composable.
| Name | Type | Description |
|---|
Returns
Section titled “Returns”These are the properties in the object returned by the useOtpField composable.
| Name | Type | Description |
|---|---|---|
| controlId | ||
| controlProps | Ref<{ 'aria-invalid': boolean; 'aria-errormessage': string; 'aria-describedby'?: string; 'aria-label'?: string; 'aria-labelledby'?: string; id: string; role: string; }> | |
| descriptionProps | Props for the description element. | |
| errorMessage | The error message for the field. | |
| errorMessageProps | Props for the error message element. | |
| errors | The errors for the field. | |
| field | ||
| fieldSlots | ||
| fieldValue | The value of the field. | |
| isBlurred | Whether the field is blurred. | |
| isDirty | Whether the field is dirty. | |
| isDisabled | Whether the field is disabled. | |
| isTouched | Whether the field is touched. | |
| isValid | Whether the field is valid. | |
| isValidated | Whether the field is validated, used to determine whether to show validation errors or not. | |
| labelProps | Props for the label element. | |
| setBlurred | Sets the blurred state for the field. | |
| setErrors | (messages: Arrayable<string>) => void | Sets the errors for the field. |
| setIsValidated | Sets the validated state for the field. | |
| setTouched | Sets the touched state for the field. | |
| setValue | Sets the value for the field. | |
| submitErrorMessage | The error message for the field from the last submit attempt. | |
| submitErrors | The errors for the field from the last submit attempt. | |
| validate | (mutate?: boolean) => Promise<ValidationResult<unknown>> | Validates the field. |
useOtpSlot
Section titled “useOtpSlot”These are the properties that can be passed to the useOtpSlot composable.
| Name | Type | Description |
|---|---|---|
| accept | The type of the slot. | |
| disabled | Whether the slot is disabled. | |
| masked | Whether the slot is masked. | |
| readonly | Whether the slot is readonly. | |
| value | The value of the slot. |
Returns
Section titled “Returns”These are the properties in the object returned by the useOtpSlot composable.
| Name | Type | Description |
|---|---|---|
| key | ||
| slotProps | ||
| value |