OTP Field
A one-time password input composed of individual character slots.
View as MarkdownNote: OTP Field is currently in preview. Its API may change before it becomes stable.
Enter the 6-character code we sent to your device.
Usage guidelines
- Form controls must have an accessible name: It can be created using a
<label>element or theFieldcomponent. See Labeling an OTP field and the forms guide.
Anatomy
Import the component and assemble its parts:
import { OTPFieldPreview as OTPField } from '@base-ui/react/otp-field';
<OTPField.Root>
<OTPField.Input />
</OTPField.Root>Examples
Labeling an OTP field
Pass an id to <OTPField.Root> and use a native <label> with a matching htmlFor. Let the
first input use the field label, and add aria-label to the remaining inputs so assistive
technology can announce which slot is focused.
Optionally, add aria-describedby when supporting text should be announced with the field.
<div>
<label htmlFor="verification-code">Verification code</label>
<OTPField.Root id="verification-code" length={6} aria-describedby="verification-code-description">
<OTPField.Input />
<OTPField.Input aria-label="Character 2 of 6" />
<OTPField.Input aria-label="Character 3 of 6" />
<OTPField.Input aria-label="Character 4 of 6" />
<OTPField.Input aria-label="Character 5 of 6" />
<OTPField.Input aria-label="Character 6 of 6" />
</OTPField.Root>
<p id="verification-code-description">Enter the 6-character code we sent to your device.</p>
</div>Form integration
Use Field to handle label associations and form integration:
<Form>
<Field.Root name="verificationCode">
<Field.Label>Verification code</Field.Label>
<Field.Description>Enter the 6-character code we sent to your device.</Field.Description>
<OTPField.Root length={6}>
<OTPField.Input />
<OTPField.Input aria-label="Character 2 of 6" />
<OTPField.Input aria-label="Character 3 of 6" />
<OTPField.Input aria-label="Character 4 of 6" />
<OTPField.Input aria-label="Character 5 of 6" />
<OTPField.Input aria-label="Character 6 of 6" />
</OTPField.Root>
</Field.Root>
</Form>Pass autoSubmit to submit the owning form automatically when all slots are filled, or use
onValueComplete to react to completion without submitting.
Alphanumeric verification codes
Use validationType="alphanumeric" for recovery, backup, or invite codes that mix letters and
numbers.
Accept letters and numbers for backup codes such as A7C9XZ.
Grouped layouts
Wrap subsets of inputs in your own layout elements and use <OTPField.Separator> when you
want the code presented in smaller visual chunks such as 123-456.
Placeholder hints
<OTPField.Input> is a real input, so native placeholder props and CSS work as usual. This
example keeps placeholder hints visible until the active slot receives focus.
Placeholder hints can stay visible until the active slot is focused.
Custom sanitization
Set validationType="none" with sanitizeValue when you need to normalize pasted values before
they reach state or apply custom validation rules. Use inputMode when a custom rule still needs a
specific virtual keyboard hint, and onValueInvalid when you want to react to rejected
characters.
Digits 0-3 only.
Masked entry
Use mask when the code should be obscured while it is being typed.
Use mask to obscure the code on shared screens.
API reference
Root
Groups all OTP field parts and manages their state.
Renders a <div> element.
namestring—
- Name
- Description
Identifies the field when a form is submitted.
- Type
string | undefined
defaultValuestring—
- Name
- Description
The uncontrolled OTP value when the component is initially rendered.
- Type
string | undefined
valuestring—
- Name
- Description
The OTP value.
- Type
string | undefined
onValueChangefunction—
- Name
- Description
Callback fired when the OTP value changes.
The
eventDetails.reasonindicates what triggered the change:'input-change'for typing or autofill'input-clear'when a character is removed by text input'input-paste'for paste interactions'keyboard'for keyboard interactions that change the value
- Type
| (( value: string, eventDetails: OTPFieldPreview.Root.ChangeEventDetails, ) => void) | undefined
autoCompletestring'one-time-code'
- Name
- Description
The input autocomplete attribute. Applied to the first slot and hidden validation input.
- Type
string | undefined- Default
'one-time-code'
autoSubmitbooleanfalse
- Name
- Description
Whether to submit the owning form when the OTP becomes complete.
- Type
boolean | undefined- Default
false
formstring—
- Name
- Description
A string specifying the
formelement with which the hidden input is associated. This string’s value must match the id of aformelement in the same document.- Type
string | undefined
inputModeUnion—
- Name
- Description
The virtual keyboard hint applied to the slot inputs and hidden validation input.
Built-in validation modes provide sensible defaults, but you can override them when needed.
- Type
| 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search' | undefined
length*number
—
number- Name
- Description
The number of OTP input slots. Required so the root can clamp values, detect completion, and generate consistent validation markup before all slots hydrate.
- Type
number
maskbooleanfalse
- Name
- Description
Whether the slot inputs should mask entered characters. Pass
typedirectly to individual<OTPField.Input>parts to use a custom input type.- Type
boolean | undefined- Default
false
onValueCompletefunction—
- Name
- Description
Callback function that is fired when the OTP value becomes complete.
It runs later than
onValueChange, after the internal value update is applied.If
autoSubmitis enabled, it runs immediately before the owning form is submitted.- Type
| (( value: string, eventDetails: OTPFieldPreview.Root.CompleteEventDetails, ) => void) | undefined
onValueInvalidfunction—
- Name
- Description
Callback fired when entered text contains characters that are rejected by sanitization, before the OTP value updates.
The
valueargument is the attempted user-entered string before sanitization.- Type
| (( value: string, eventDetails: OTPFieldPreview.Root.InvalidEventDetails, ) => void) | undefined
sanitizeValuefunction—
- Name
- Description
Function for custom sanitization when
validationTypeis set to'none'. This function runs before updating the OTP value from user interactions.- Type
((value: string) => string) | undefined
validationTypeOTPFieldPreview.Root.ValidationType'numeric'
- Name
- Description
The type of input validation to apply to the OTP value.
- Type
OTPFieldPreview.Root.ValidationType | undefined- Default
'numeric'
disabledbooleanfalse
- Name
- Description
Whether the component should ignore user interaction.
- Type
boolean | undefined- Default
false
readOnlybooleanfalse
- Name
- Description
Whether the user should be unable to change the field value.
- Type
boolean | undefined- Default
false
requiredbooleanfalse
- Name
- Description
Whether the user must enter a value before submitting a form.
- Type
boolean | undefined- Default
false
idstring—
- Name
- Description
The id of the first input element. Subsequent inputs derive their ids from it (
{id}-2,{id}-3, and so on).- Type
string | undefined
classNamestring | function—
- Name
- Description
CSS class applied to the element, or a function that returns a class based on the component’s state.
- Type
| string | ((state: OTPFieldRootState) => string | undefined) | undefined
styleReact.CSSProperties | function—
- Name
- Description
Style applied to the element, or a function that returns a style object based on the component’s state.
- Type
| React.CSSProperties | (( state: OTPFieldRootState, ) => React.CSSProperties | undefined) | undefined
renderReactElement | function—
- Name
- Description
Allows you to replace the component’s HTML element with a different tag, or compose it with another component.
Accepts a
ReactElementor a function that returns the element to render.- Type
| ReactElement | (( props: HTMLProps, state: OTPFieldRootState, ) => ReactElement) | undefined
data-disabled
Present when the OTP field is disabled.
data-readonly
Present when the OTP field is readonly.
data-required
Present when the OTP field is required.
data-valid
Present when the OTP field is in valid state (when wrapped in Field.Root).
data-invalid
Present when the OTP field is in invalid state (when wrapped in Field.Root).
data-dirty
Present when the OTP field’s value has changed (when wrapped in Field.Root).
data-touched
Present when the OTP field has been touched (when wrapped in Field.Root).
data-complete
Present when all slots are filled.
data-filled
Present when the OTP field contains at least one character.
data-focused
Present when one of the OTP field inputs is focused.
| Attribute | Description | |
|---|---|---|
data-disabled | Present when the OTP field is disabled. | |
data-readonly | Present when the OTP field is readonly. | |
data-required | Present when the OTP field is required. | |
data-valid | Present when the OTP field is in valid state (when wrapped in Field.Root). | |
data-invalid | Present when the OTP field is in invalid state (when wrapped in Field.Root). | |
data-dirty | Present when the OTP field’s value has changed (when wrapped in Field.Root). | |
data-touched | Present when the OTP field has been touched (when wrapped in Field.Root). | |
data-complete | Present when all slots are filled. | |
data-filled | Present when the OTP field contains at least one character. | |
data-focused | Present when one of the OTP field inputs is focused. | |
OTPFieldPreview.Root.StateHide
type OTPFieldPreviewRootState = {
/** Whether all slots are filled. */
complete: boolean;
/** Whether the component should ignore user interaction. */
disabled: boolean;
/** The number of OTP input slots. */
length: number;
/** Whether the user should be unable to change the field value. */
readOnly: boolean;
/** Whether the user must enter a value before submitting a form. */
required: boolean;
/** The OTP value. */
value: string;
/** Whether the field has been touched. */
touched: boolean;
/** Whether the field value has changed from its initial value. */
dirty: boolean;
/** Whether the field is valid. */
valid: boolean | null;
/** Whether the field has a value. */
filled: boolean;
/** Whether the field is focused. */
focused: boolean;
}OTPFieldPreview.Root.ChangeEventReasonHide
type OTPFieldPreviewRootChangeEventReason =
| 'input-change'
| 'input-clear'
| 'input-paste'
| 'keyboard'OTPFieldPreview.Root.ChangeEventDetailsHide
type OTPFieldPreviewRootChangeEventDetails = (
| { reason: 'input-change'; event: InputEvent | Event }
| { reason: 'input-clear'; event: InputEvent | Event | FocusEvent }
| { reason: 'input-paste'; event: ClipboardEvent }
| { reason: 'keyboard'; event: KeyboardEvent }
) & {
/** Cancels Base UI from handling the event. */
cancel: () => void;
/** Allows the event to propagate in cases where Base UI will stop the propagation. */
allowPropagation: () => void;
/** Indicates whether the event has been canceled. */
isCanceled: boolean;
/** Indicates whether the event is allowed to propagate. */
isPropagationAllowed: boolean;
/** The element that triggered the event, if applicable. */
trigger: Element | undefined;
}OTPFieldPreview.Root.CompleteEventDetailsHide
type OTPFieldPreviewRootCompleteEventDetails =
| { reason: 'input-change'; event: InputEvent | Event }
| { reason: 'input-paste'; event: ClipboardEvent }
| { reason: 'keyboard'; event: KeyboardEvent }OTPFieldPreview.Root.CompleteEventReasonHide
type OTPFieldPreviewRootCompleteEventReason = 'input-change' | 'input-paste' | 'keyboard'OTPFieldPreview.Root.InvalidEventDetailsHide
type OTPFieldPreviewRootInvalidEventDetails =
| { reason: 'input-change'; event: InputEvent | Event }
| { reason: 'input-paste'; event: ClipboardEvent }OTPFieldPreview.Root.InvalidEventReasonHide
type OTPFieldPreviewRootInvalidEventReason = 'input-change' | 'input-paste'OTPFieldPreview.Root.ValidationTypeHide
type OTPFieldPreviewRootValidationType = 'numeric' | 'alpha' | 'alphanumeric' | 'none'Input
An individual OTP character input.
Renders an <input> element.
classNamestring | function—
- Name
- Description
CSS class applied to the element, or a function that returns a class based on the component’s state.
- Type
| string | ((state: OTPFieldInputState) => string | undefined) | undefined
styleReact.CSSProperties | function—
- Name
- Description
Style applied to the element, or a function that returns a style object based on the component’s state.
- Type
| React.CSSProperties | (( state: OTPFieldInputState, ) => React.CSSProperties | undefined) | undefined
renderReactElement | function—
- Name
- Description
Allows you to replace the component’s HTML element with a different tag, or compose it with another component.
Accepts a
ReactElementor a function that returns the element to render.- Type
| ReactElement | (( props: HTMLProps, state: OTPFieldInputState, ) => ReactElement) | undefined
data-disabled
Present when the OTP field is disabled.
data-readonly
Present when the OTP field is readonly.
data-required
Present when the OTP field is required.
data-valid
Present when the OTP field is in valid state (when wrapped in Field.Root).
data-invalid
Present when the OTP field is in invalid state (when wrapped in Field.Root).
data-dirty
Present when the OTP field’s value has changed (when wrapped in Field.Root).
data-touched
Present when the OTP field has been touched (when wrapped in Field.Root).
data-complete
Present when all slots are filled.
data-filled
Present when the input contains a character.
data-focused
Present when any OTP field input is focused.
| Attribute | Description | |
|---|---|---|
data-disabled | Present when the OTP field is disabled. | |
data-readonly | Present when the OTP field is readonly. | |
data-required | Present when the OTP field is required. | |
data-valid | Present when the OTP field is in valid state (when wrapped in Field.Root). | |
data-invalid | Present when the OTP field is in invalid state (when wrapped in Field.Root). | |
data-dirty | Present when the OTP field’s value has changed (when wrapped in Field.Root). | |
data-touched | Present when the OTP field has been touched (when wrapped in Field.Root). | |
data-complete | Present when all slots are filled. | |
data-filled | Present when the input contains a character. | |
data-focused | Present when any OTP field input is focused. | |
OTPFieldPreview.Input.StateHide
type OTPFieldPreviewInputState = {
/** Whether this input contains a character. */
filled: boolean;
/** The input index. */
index: number;
/** The character rendered in this slot. */
value: string;
/** Whether the component should ignore user interaction. */
disabled: boolean;
/** The number of OTP input slots. */
length: number;
/** Whether the user must enter a value before submitting a form. */
required: boolean;
/** Whether the user should be unable to change the field value. */
readOnly: boolean;
/** Whether all slots are filled. */
complete: boolean;
/** Whether the field has been touched. */
touched: boolean;
/** Whether the field value has changed from its initial value. */
dirty: boolean;
/** Whether the field is valid. */
valid: boolean | null;
/** Whether the field is focused. */
focused: boolean;
}Separator
A separator element accessible to screen readers.
Renders a <div> element.
orientationOrientation'horizontal'
- Name
- Description
The orientation of the separator.
- Type
'horizontal' | 'vertical' | undefined- Default
'horizontal'
classNamestring | function—
- Name
- Description
CSS class applied to the element, or a function that returns a class based on the component’s state.
- Type
| string | ((state: SeparatorState) => string | undefined) | undefined
styleReact.CSSProperties | function—
- Name
- Description
Style applied to the element, or a function that returns a style object based on the component’s state.
- Type
| React.CSSProperties | (( state: SeparatorState, ) => React.CSSProperties | undefined) | undefined
renderReactElement | function—
- Name
- Description
Allows you to replace the component’s HTML element with a different tag, or compose it with another component.
Accepts a
ReactElementor a function that returns the element to render.- Type
| ReactElement | (( props: HTMLProps, state: SeparatorState, ) => ReactElement) | undefined
OTPFieldPreview.Separator.StateHide
type OTPFieldPreviewSeparatorState = {
/** The orientation of the separator. */
orientation: 'horizontal' | 'vertical';
}