---
title: OTP Field
subtitle: A one-time password input composed of individual character slots.
description: A high-quality, unstyled React OTP field component for one-time password and verification code entry.
---

# OTP Field

A high-quality, unstyled React OTP field component for one-time password and verification code entry.

> **Note:** OTP Field is currently in preview. Its API may change before it becomes stable.

## Demo

### Tailwind

This example shows how to implement the component using Tailwind CSS.

```tsx
/* index.tsx */
import * as React from 'react';
import { OTPFieldPreview as OTPField } from '@base-ui/react/otp-field';

const OTP_LENGTH = 6;

export default function ExampleOTPField() {
  const id = React.useId();
  const descriptionId = `${id}-description`;

  return (
    <div className="flex w-full max-w-80 flex-col items-start gap-1">
      <label htmlFor={id} className="text-sm font-bold text-gray-900">
        Verification code
      </label>
      <OTPField.Root
        id={id}
        length={OTP_LENGTH}
        aria-describedby={descriptionId}
        className="flex w-full gap-2"
      >
        {Array.from({ length: OTP_LENGTH }, (_, index) => (
          <OTPField.Input
            key={index}
            className="box-border m-0 h-11 w-10 rounded-lg border border-gray-300 bg-transparent text-center font-inherit text-lg font-medium text-gray-900 outline-none focus:outline-solid focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800"
            aria-label={`Character ${index + 1} of ${OTP_LENGTH}`}
          />
        ))}
      </OTPField.Root>
      <p id={descriptionId} className="m-0 text-sm text-gray-600">
        Enter the 6-character code we sent to your device.
      </p>
    </div>
  );
}
```

### CSS Modules

This example shows how to implement the component using CSS Modules.

```css
/* index.module.css */
.Field {
  display: flex;
  flex-direction: column;
  align-items: start;
  gap: 0.25rem;
  width: 100%;
  max-width: 20rem;
}

.Label {
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 700;
  color: var(--color-gray-900);
}

.Root {
  display: flex;
  gap: 0.5rem;
  width: 100%;
}

.Input {
  box-sizing: border-box;
  margin: 0;
  border: 1px solid var(--color-gray-300);
  width: 2.5rem;
  height: 2.75rem;
  border-radius: 0.5rem;
  font-family: inherit;
  font-size: 1.125rem;
  font-weight: 500;
  text-align: center;
  background-color: transparent;
  color: var(--color-gray-900);

  &:focus {
    outline: none;
    outline: 2px solid var(--color-blue);
    outline-offset: -1px;
  }
}

.Description {
  margin: 0;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
}
```

```tsx
/* index.tsx */
import * as React from 'react';
import { OTPFieldPreview as OTPField } from '@base-ui/react/otp-field';
import styles from './index.module.css';

const OTP_LENGTH = 6;

export default function ExampleOTPField() {
  const id = React.useId();
  const descriptionId = `${id}-description`;

  return (
    <div className={styles.Field}>
      <label htmlFor={id} className={styles.Label}>
        Verification code
      </label>
      <OTPField.Root
        id={id}
        length={OTP_LENGTH}
        aria-describedby={descriptionId}
        className={styles.Root}
      >
        {Array.from({ length: OTP_LENGTH }, (_, index) => (
          <OTPField.Input
            key={index}
            className={styles.Input}
            aria-label={`Character ${index + 1} of ${OTP_LENGTH}`}
          />
        ))}
      </OTPField.Root>
      <p id={descriptionId} className={styles.Description}>
        Enter the 6-character code we sent to your device.
      </p>
    </div>
  );
}
```

## Usage guidelines

- **Form controls must have an accessible name**: It can be created using a `<label>` element or the `Field` component. See [Labeling an OTP field](/react/components/otp-field.md) and the [forms guide](/react/handbook/forms.md).

## Anatomy

Import the component and assemble its parts:

```jsx title="Anatomy"
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.

```tsx title="OTP Field with a native label and description"
<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](/react/components/field.md) to handle label associations and form integration:

```tsx title="Using OTP Field in a form" {2}
<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.

## Demo

### Tailwind

This example shows how to implement the component using Tailwind CSS.

```tsx
/* index.tsx */
import * as React from 'react';
import { OTPFieldPreview as OTPField } from '@base-ui/react/otp-field';

const CODE_LENGTH = 6;

export default function OTPFieldAlphanumericDemo() {
  const id = React.useId();
  const descriptionId = `${id}-description`;

  return (
    <div className="flex w-full max-w-80 flex-col items-start gap-1">
      <label htmlFor={id} className="text-sm font-bold text-gray-900">
        Recovery code
      </label>
      <OTPField.Root
        id={id}
        length={CODE_LENGTH}
        validationType="alphanumeric"
        aria-describedby={descriptionId}
        className="flex w-full gap-2"
      >
        {Array.from({ length: CODE_LENGTH }, (_, index) => (
          <OTPField.Input
            key={index}
            className="box-border m-0 h-11 w-10 rounded-lg border border-gray-300 bg-transparent text-center font-inherit text-lg font-medium text-gray-900 outline-none focus:outline-solid focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800"
            aria-label={`Character ${index + 1} of ${CODE_LENGTH}`}
          />
        ))}
      </OTPField.Root>
      <p id={descriptionId} className="m-0 text-sm text-gray-600">
        Accept letters and numbers for backup codes such as <code>A7C9XZ</code>.
      </p>
    </div>
  );
}
```

### CSS Modules

This example shows how to implement the component using CSS Modules.

```css
/* index.module.css */
.Field {
  display: flex;
  flex-direction: column;
  align-items: start;
  gap: 0.25rem;
  width: 100%;
  max-width: 20rem;
}

.Label {
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 700;
  color: var(--color-gray-900);
}

.Root {
  display: flex;
  gap: 0.5rem;
  width: 100%;
}

.Input {
  box-sizing: border-box;
  margin: 0;
  border: 1px solid var(--color-gray-300);
  width: 2.5rem;
  height: 2.75rem;
  border-radius: 0.5rem;
  font-family: inherit;
  font-size: 1.125rem;
  font-weight: 500;
  text-align: center;
  background-color: transparent;
  color: var(--color-gray-900);

  &:focus {
    outline: none;
    outline: 2px solid var(--color-blue);
    outline-offset: -1px;
  }
}

.Description {
  margin: 0;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
}

.Code {
  font-family:
    ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
}
```

```tsx
/* index.tsx */
import * as React from 'react';
import { OTPFieldPreview as OTPField } from '@base-ui/react/otp-field';
import styles from './index.module.css';

const CODE_LENGTH = 6;

export default function OTPFieldAlphanumericDemo() {
  const id = React.useId();
  const descriptionId = `${id}-description`;

  return (
    <div className={styles.Field}>
      <label htmlFor={id} className={styles.Label}>
        Recovery code
      </label>
      <OTPField.Root
        id={id}
        length={CODE_LENGTH}
        validationType="alphanumeric"
        aria-describedby={descriptionId}
        className={styles.Root}
      >
        {Array.from({ length: CODE_LENGTH }, (_, index) => (
          <OTPField.Input
            key={index}
            className={styles.Input}
            aria-label={`Character ${index + 1} of ${CODE_LENGTH}`}
          />
        ))}
      </OTPField.Root>
      <p id={descriptionId} className={styles.Description}>
        Accept letters and numbers for backup codes such as{' '}
        <span className={styles.Code}>A7C9XZ</span>.
      </p>
    </div>
  );
}
```

### 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`.

## Demo

### Tailwind

This example shows how to implement the component using Tailwind CSS.

```tsx
/* index.tsx */
import * as React from 'react';
import { OTPFieldPreview as OTPField } from '@base-ui/react/otp-field';

const OTP_LENGTH = 6;

export default function OTPFieldGroupedDemo() {
  const id = React.useId();

  return (
    <div className="flex w-full max-w-80 flex-col items-start gap-1">
      <label htmlFor={id} className="text-sm font-bold text-gray-900">
        Verification code
      </label>
      <OTPField.Root id={id} length={OTP_LENGTH} className="flex w-full items-center gap-2">
        <div className="flex gap-2">
          {Array.from({ length: 3 }, (_, index) => (
            <OTPField.Input
              key={index}
              className="box-border m-0 h-11 w-10 rounded-lg border border-gray-300 bg-transparent text-center font-inherit text-lg font-medium text-gray-900 outline-none focus:outline-solid focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800"
              aria-label={`Character ${index + 1} of ${OTP_LENGTH}`}
            />
          ))}
        </div>
        <OTPField.Separator className="flex w-4 items-center justify-center text-gray-500">
          <SeparatorMark />
        </OTPField.Separator>
        <div className="flex gap-2">
          {Array.from({ length: 3 }, (_, index) => (
            <OTPField.Input
              key={index + 3}
              className="box-border m-0 h-11 w-10 rounded-lg border border-gray-300 bg-transparent text-center font-inherit text-lg font-medium text-gray-900 outline-none focus:outline-solid focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800"
              aria-label={`Character ${index + 4} of ${OTP_LENGTH}`}
            />
          ))}
        </div>
      </OTPField.Root>
    </div>
  );
}

function SeparatorMark(props: React.ComponentProps<'svg'>) {
  return (
    <svg aria-hidden="true" viewBox="0 0 20 4" width="20" height="4" fill="none" {...props}>
      <path d="M2 2H18" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" />
    </svg>
  );
}
```

### CSS Modules

This example shows how to implement the component using CSS Modules.

```css
/* index.module.css */
.Field {
  display: flex;
  flex-direction: column;
  align-items: start;
  gap: 0.25rem;
  width: 100%;
  max-width: 20rem;
}

.Label {
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 700;
  color: var(--color-gray-900);
}

.Root {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
}

.Group {
  display: flex;
  gap: 0.5rem;
}

.Input {
  box-sizing: border-box;
  margin: 0;
  border: 1px solid var(--color-gray-300);
  width: 2.5rem;
  height: 2.75rem;
  border-radius: 0.5rem;
  font-family: inherit;
  font-size: 1.125rem;
  font-weight: 500;
  text-align: center;
  background-color: transparent;
  color: var(--color-gray-900);

  &:focus {
    outline: none;
    outline: 2px solid var(--color-blue);
    outline-offset: -1px;
  }
}

.Separator {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1rem;
  color: var(--color-gray-500);
}

.SeparatorIcon {
  display: block;
}
```

```tsx
/* index.tsx */
import * as React from 'react';
import { OTPFieldPreview as OTPField } from '@base-ui/react/otp-field';
import styles from './index.module.css';

const OTP_LENGTH = 6;

export default function OTPFieldGroupedDemo() {
  const id = React.useId();

  return (
    <div className={styles.Field}>
      <label htmlFor={id} className={styles.Label}>
        Verification code
      </label>
      <OTPField.Root id={id} length={OTP_LENGTH} className={styles.Root}>
        <div className={styles.Group}>
          {Array.from({ length: 3 }, (_, index) => (
            <OTPField.Input
              key={index}
              className={styles.Input}
              aria-label={`Character ${index + 1} of ${OTP_LENGTH}`}
            />
          ))}
        </div>
        <OTPField.Separator className={styles.Separator}>
          <SeparatorMark className={styles.SeparatorIcon} />
        </OTPField.Separator>
        <div className={styles.Group}>
          {Array.from({ length: 3 }, (_, index) => (
            <OTPField.Input
              key={index + 3}
              className={styles.Input}
              aria-label={`Character ${index + 4} of ${OTP_LENGTH}`}
            />
          ))}
        </div>
      </OTPField.Root>
    </div>
  );
}

function SeparatorMark(props: React.ComponentProps<'svg'>) {
  return (
    <svg aria-hidden="true" viewBox="0 0 20 4" width="20" height="4" fill="none" {...props}>
      <path d="M2 2H18" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" />
    </svg>
  );
}
```

### 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.

## Demo

### Tailwind

This example shows how to implement the component using Tailwind CSS.

```tsx
/* index.tsx */
import * as React from 'react';
import { OTPFieldPreview as OTPField } from '@base-ui/react/otp-field';

const CODE_LENGTH = 6;

export default function OTPFieldFocusedPlaceholderDemo() {
  const id = React.useId();
  const descriptionId = `${id}-description`;

  return (
    <div className="flex w-full max-w-80 flex-col items-start gap-1">
      <label htmlFor={id} className="text-sm font-bold text-gray-900">
        Verification code
      </label>
      <OTPField.Root
        id={id}
        length={CODE_LENGTH}
        aria-describedby={descriptionId}
        className="flex w-full gap-2"
      >
        {Array.from({ length: CODE_LENGTH }, (_, index) => (
          <OTPField.Input
            key={index}
            className="box-border m-0 h-11 w-10 rounded-lg border border-gray-300 bg-transparent text-center font-inherit text-lg font-medium text-gray-900 outline-none placeholder:text-gray-400 focus:outline-solid focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800 focus:placeholder:text-transparent"
            placeholder="•"
            aria-label={`Character ${index + 1} of ${CODE_LENGTH}`}
          />
        ))}
      </OTPField.Root>
      <p id={descriptionId} className="m-0 text-sm text-gray-600">
        Placeholder hints can stay visible until the active slot is focused.
      </p>
    </div>
  );
}
```

### CSS Modules

This example shows how to implement the component using CSS Modules.

```css
/* index.module.css */
.Field {
  display: flex;
  flex-direction: column;
  align-items: start;
  gap: 0.25rem;
  width: 100%;
  max-width: 20rem;
}

.Label {
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 700;
  color: var(--color-gray-900);
}

.Root {
  display: flex;
  gap: 0.5rem;
  width: 100%;
}

.Input {
  box-sizing: border-box;
  margin: 0;
  border: 1px solid var(--color-gray-300);
  width: 2.5rem;
  height: 2.75rem;
  border-radius: 0.5rem;
  font-family: inherit;
  font-size: 1.125rem;
  font-weight: 500;
  text-align: center;
  background-color: transparent;
  color: var(--color-gray-900);

  &::placeholder {
    color: var(--color-gray-400);
  }

  &:focus {
    outline: none;
    outline: 2px solid var(--color-blue);
    outline-offset: -1px;
  }

  &:focus::placeholder {
    color: transparent;
  }
}

.Description {
  margin: 0;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
}
```

```tsx
/* index.tsx */
import * as React from 'react';
import { OTPFieldPreview as OTPField } from '@base-ui/react/otp-field';
import styles from './index.module.css';

const CODE_LENGTH = 6;

export default function OTPFieldFocusedPlaceholderDemo() {
  const id = React.useId();
  const descriptionId = `${id}-description`;

  return (
    <div className={styles.Field}>
      <label htmlFor={id} className={styles.Label}>
        Verification code
      </label>
      <OTPField.Root
        id={id}
        length={CODE_LENGTH}
        aria-describedby={descriptionId}
        className={styles.Root}
      >
        {Array.from({ length: CODE_LENGTH }, (_, index) => (
          <OTPField.Input
            key={index}
            className={styles.Input}
            placeholder="•"
            aria-label={`Character ${index + 1} of ${CODE_LENGTH}`}
          />
        ))}
      </OTPField.Root>
      <p id={descriptionId} className={styles.Description}>
        Placeholder hints can stay visible until the active slot is focused.
      </p>
    </div>
  );
}
```

### 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.

## Demo

### CSS Modules

This example shows how to implement the component using CSS Modules.

```css
/* index.module.css */
.Field {
  display: flex;
  flex-direction: column;
  align-items: start;
  gap: 0.25rem;
  width: 100%;
  max-width: 20rem;
}

.Label {
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 700;
  color: var(--color-gray-900);
}

.Root {
  display: flex;
  gap: 0.5rem;
  width: 100%;
}

.Input {
  box-sizing: border-box;
  margin: 0;
  border: 1px solid var(--color-gray-300);
  width: 2.5rem;
  height: 2.75rem;
  border-radius: 0.5rem;
  font-family: inherit;
  font-size: 1.125rem;
  font-weight: 500;
  text-align: center;
  background-color: transparent;
  color: var(--color-gray-900);

  &:focus {
    outline: none;
    outline: 2px solid var(--color-blue);
    outline-offset: -1px;
  }
}

.InputInvalidA {
  border-color: var(--color-red);
  outline: 2px solid var(--color-red-800);
  outline-offset: -1px;
  animation: otp-field-shake-a 180ms ease-in-out;

  &:focus {
    outline: 2px solid var(--color-red-800);
    outline-offset: -1px;
  }
}

.InputInvalidB {
  border-color: var(--color-red);
  outline: 2px solid var(--color-red-800);
  outline-offset: -1px;
  animation: otp-field-shake-b 180ms ease-in-out;

  &:focus {
    outline: 2px solid var(--color-red-800);
    outline-offset: -1px;
  }
}

.Description {
  margin: 0;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
}

.Code {
  font-family:
    ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
}

.ScreenReaderOnly {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

@keyframes otp-field-shake-a {
  0%,
  100% {
    transform: translateX(0);
  }

  25% {
    transform: translateX(-4px);
  }

  75% {
    transform: translateX(4px);
  }
}

@keyframes otp-field-shake-b {
  0%,
  100% {
    transform: translateX(0);
  }

  25% {
    transform: translateX(-4px);
  }

  75% {
    transform: translateX(4px);
  }
}
```

```tsx
/* index.tsx */
'use client';
import * as React from 'react';
import { OTPFieldPreview as OTPField } from '@base-ui/react/otp-field';
import { useInvalidFeedback } from './useInvalidFeedback';
import styles from './index.module.css';

const CODE_LENGTH = 6;

function sanitizeTierCode(value: string) {
  return value.replace(/[^0-3]/g, '');
}

function getInvalidClassName(invalidPulse: number, evenClassName: string, oddClassName: string) {
  if (invalidPulse === 0) {
    return '';
  }

  return invalidPulse % 2 === 0 ? evenClassName : oddClassName;
}

export default function OTPFieldCustomSanitizeDemo() {
  const id = React.useId();
  const descriptionId = `${id}-description`;

  const {
    activeInvalidIndex,
    handleValueChange,
    handleValueInvalid,
    invalidPulse,
    setFocusedIndex,
    statusMessage,
  } = useInvalidFeedback();

  const invalidClassName = getInvalidClassName(
    invalidPulse,
    styles.InputInvalidB,
    styles.InputInvalidA,
  );

  return (
    <div className={styles.Field}>
      <label htmlFor={id} className={styles.Label}>
        Tier code
      </label>
      <OTPField.Root
        id={id}
        length={CODE_LENGTH}
        validationType="none"
        inputMode="numeric"
        sanitizeValue={sanitizeTierCode}
        onValueChange={handleValueChange}
        onValueInvalid={handleValueInvalid}
        aria-describedby={descriptionId}
        className={styles.Root}
      >
        {Array.from({ length: CODE_LENGTH }, (_, index) => (
          <OTPField.Input
            key={index}
            className={`${styles.Input} ${activeInvalidIndex === index ? invalidClassName : ''}`.trim()}
            aria-label={`Character ${index + 1} of ${CODE_LENGTH}`}
            onFocus={() => {
              setFocusedIndex(index);
            }}
          />
        ))}
      </OTPField.Root>
      <p id={descriptionId} className={styles.Description}>
        Digits <span className={styles.Code}>0-3</span> only.
      </p>
      <span aria-live="polite" className={styles.ScreenReaderOnly}>
        {statusMessage}
      </span>
    </div>
  );
}
```

```ts
/* useInvalidFeedback.ts */
'use client';
import * as React from 'react';

export function useInvalidFeedback() {
  const [focusedIndex, setFocusedIndex] = React.useState(0);
  const [invalidPulse, setInvalidPulse] = React.useState(0);
  const [statusMessage, setStatusMessage] = React.useState('');
  const invalidTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
  const skipClearOnNextValueChangeRef = React.useRef(false);

  React.useEffect(() => {
    return () => {
      if (invalidTimeoutRef.current != null) {
        clearTimeout(invalidTimeoutRef.current);
      }
    };
  }, []);

  function clearInvalidFeedback() {
    if (invalidTimeoutRef.current != null) {
      clearTimeout(invalidTimeoutRef.current);
      invalidTimeoutRef.current = null;
    }

    setInvalidPulse(0);
    setStatusMessage('');
  }

  function handleValueChange() {
    if (skipClearOnNextValueChangeRef.current) {
      skipClearOnNextValueChangeRef.current = false;
      return;
    }

    clearInvalidFeedback();
  }

  function handleValueInvalid(value: string) {
    skipClearOnNextValueChangeRef.current = true;
    setInvalidPulse((current) => current + 1);
    setStatusMessage(`Unsupported characters were ignored from ${value}.`);

    if (invalidTimeoutRef.current != null) {
      clearTimeout(invalidTimeoutRef.current);
    }

    invalidTimeoutRef.current = setTimeout(() => {
      invalidTimeoutRef.current = null;
      setInvalidPulse(0);
      setStatusMessage('');
    }, 400);
  }

  return {
    activeInvalidIndex: invalidPulse > 0 ? focusedIndex : -1,
    invalidPulse,
    statusMessage,
    setFocusedIndex,
    handleValueChange,
    handleValueInvalid,
  };
}
```

### Masked entry

Use `mask` when the code should be obscured while it is being typed.

## Demo

### Tailwind

This example shows how to implement the component using Tailwind CSS.

```tsx
/* index.tsx */
import * as React from 'react';
import { OTPFieldPreview as OTPField } from '@base-ui/react/otp-field';

const CODE_LENGTH = 6;

export default function OTPFieldPasswordDemo() {
  const id = React.useId();
  const descriptionId = `${id}-description`;

  return (
    <div className="flex w-full max-w-80 flex-col items-start gap-1">
      <label htmlFor={id} className="text-sm font-bold text-gray-900">
        Access code
      </label>
      <OTPField.Root
        id={id}
        length={CODE_LENGTH}
        mask
        aria-describedby={descriptionId}
        className="flex w-full gap-2"
      >
        {Array.from({ length: CODE_LENGTH }, (_, index) => (
          <OTPField.Input
            key={index}
            className="box-border m-0 h-11 w-10 rounded-lg border border-gray-300 bg-transparent text-center font-inherit text-lg font-medium text-gray-900 outline-none focus:outline-solid focus:outline-2 focus:-outline-offset-1 focus:outline-blue-800"
            aria-label={`Character ${index + 1} of ${CODE_LENGTH}`}
          />
        ))}
      </OTPField.Root>
      <p id={descriptionId} className="m-0 text-sm text-gray-600">
        Use <code>mask</code> to obscure the code on shared screens.
      </p>
    </div>
  );
}
```

### CSS Modules

This example shows how to implement the component using CSS Modules.

```css
/* index.module.css */
.Field {
  display: flex;
  flex-direction: column;
  align-items: start;
  gap: 0.25rem;
  width: 100%;
  max-width: 20rem;
}

.Label {
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 700;
  color: var(--color-gray-900);
}

.Root {
  display: flex;
  gap: 0.5rem;
  width: 100%;
}

.Input {
  box-sizing: border-box;
  margin: 0;
  border: 1px solid var(--color-gray-300);
  width: 2.5rem;
  height: 2.75rem;
  border-radius: 0.5rem;
  font-family: inherit;
  font-size: 1.125rem;
  font-weight: 500;
  text-align: center;
  background-color: transparent;
  color: var(--color-gray-900);

  &:focus {
    outline: none;
    outline: 2px solid var(--color-blue);
    outline-offset: -1px;
  }
}

.Description {
  margin: 0;
  font-size: 0.875rem;
  line-height: 1.25rem;
  color: var(--color-gray-600);
}

.Code {
  font-family:
    ui-monospace, SFMono-Regular, 'SF Mono', Consolas, 'Liberation Mono', Menlo, monospace;
}
```

```tsx
/* index.tsx */
import * as React from 'react';
import { OTPFieldPreview as OTPField } from '@base-ui/react/otp-field';
import styles from './index.module.css';

const CODE_LENGTH = 6;

export default function OTPFieldPasswordDemo() {
  const id = React.useId();
  const descriptionId = `${id}-description`;

  return (
    <div className={styles.Field}>
      <label htmlFor={id} className={styles.Label}>
        Access code
      </label>
      <OTPField.Root
        id={id}
        length={CODE_LENGTH}
        mask
        aria-describedby={descriptionId}
        className={styles.Root}
      >
        {Array.from({ length: CODE_LENGTH }, (_, index) => (
          <OTPField.Input
            key={index}
            className={styles.Input}
            aria-label={`Character ${index + 1} of ${CODE_LENGTH}`}
          />
        ))}
      </OTPField.Root>
      <p id={descriptionId} className={styles.Description}>
        Use <span className={styles.Code}>mask</span> to obscure the code on shared screens.
      </p>
    </div>
  );
}
```

## API reference

### Root

Groups all OTP field parts and manages their state.
Renders a `<div>` element.

**Root Props:**

| Prop            | Type                                                                                      | Default           | Description                                                                                                                                                                                                                                                                                                  |
| :-------------- | :---------------------------------------------------------------------------------------- | :---------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| name            | `string`                                                                                  | -                 | Identifies the field when a form is submitted.                                                                                                                                                                                                                                                               |
| defaultValue    | `string`                                                                                  | -                 | The uncontrolled OTP value when the component is initially rendered.                                                                                                                                                                                                                                         |
| value           | `string`                                                                                  | -                 | The OTP value.                                                                                                                                                                                                                                                                                               |
| onValueChange   | `((value: string, eventDetails: OTPFieldPreview.Root.ChangeEventDetails) => void)`        | -                 | Callback fired when the OTP value changes. The `eventDetails.reason` indicates 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 |
| autoComplete    | `string`                                                                                  | `'one-time-code'` | The input autocomplete attribute. Applied to the first slot and hidden validation input.                                                                                                                                                                                                                     |
| autoSubmit      | `boolean`                                                                                 | `false`           | Whether to submit the owning form when the OTP becomes complete.                                                                                                                                                                                                                                             |
| form            | `string`                                                                                  | -                 | A string specifying the `form` element with which the hidden input is associated.&#xA;This string's value must match the id of a `form` element in the same document.                                                                                                                                        |
| inputMode       | `'none' \| 'text' \| 'tel' \| 'url' \| 'email' \| 'numeric' \| 'decimal' \| 'search'`     | -                 | 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.                                                                                                                                |
| length\*        | `number`                                                                                  | -                 | The number of OTP input slots.&#xA;Required so the root can clamp values, detect completion, and generate&#xA;consistent validation markup before all slots hydrate.                                                                                                                                         |
| mask            | `boolean`                                                                                 | `false`           | Whether the slot inputs should mask entered characters.&#xA;Pass `type` directly to individual `<OTPField.Input>` parts to use a custom&#xA;input type.                                                                                                                                                      |
| onValueComplete | `((value: string, eventDetails: OTPFieldPreview.Root.CompleteEventDetails) => void)`      | -                 | Callback function that is fired when the OTP value becomes complete. It runs later than `onValueChange`, after the internal value update is applied. If `autoSubmit` is enabled, it runs immediately before the owning form is submitted.                                                                    |
| onValueInvalid  | `((value: string, eventDetails: OTPFieldPreview.Root.InvalidEventDetails) => void)`       | -                 | Callback fired when entered text contains characters that are rejected by sanitization,&#xA;before the OTP value updates. The `value` argument is the attempted user-entered string before sanitization.                                                                                                     |
| sanitizeValue   | `((value: string) => string)`                                                             | -                 | Function for custom sanitization when `validationType` is set to `'none'`.&#xA;This function runs before updating the OTP value from user interactions.                                                                                                                                                      |
| validationType  | `OTPFieldPreview.Root.ValidationType`                                                     | `'numeric'`       | The type of input validation to apply to the OTP value.                                                                                                                                                                                                                                                      |
| disabled        | `boolean`                                                                                 | `false`           | Whether the component should ignore user interaction.                                                                                                                                                                                                                                                        |
| readOnly        | `boolean`                                                                                 | `false`           | Whether the user should be unable to change the field value.                                                                                                                                                                                                                                                 |
| required        | `boolean`                                                                                 | `false`           | Whether the user must enter a value before submitting a form.                                                                                                                                                                                                                                                |
| id              | `string`                                                                                  | -                 | The id of the first input element.&#xA;Subsequent inputs derive their ids from it (`{id}-2`, `{id}-3`, and so on).                                                                                                                                                                                           |
| className       | `string \| ((state: OTPFieldRootState) => string \| undefined)`                           | -                 | CSS class applied to the element, or a function that&#xA;returns a class based on the component's state.                                                                                                                                                                                                     |
| style           | `React.CSSProperties \| ((state: OTPFieldRootState) => React.CSSProperties \| undefined)` | -                 | Style applied to the element, or a function that&#xA;returns a style object based on the component's state.                                                                                                                                                                                                  |
| render          | `ReactElement \| ((props: HTMLProps, state: OTPFieldRootState) => ReactElement)`          | -                 | Allows you to replace the component's HTML element&#xA;with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render.                                                                                                                |

**Root Data Attributes:**

| Attribute     | Type | 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.                         |

### Root.Props

Re-export of [Root](/react/components/otp-field.md) props.

### Root.State

```typescript
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;
};
```

### Root.ChangeEventReason

```typescript
type OTPFieldPreviewRootChangeEventReason =
  | 'input-change'
  | 'input-clear'
  | 'input-paste'
  | 'keyboard';
```

### Root.ChangeEventDetails

```typescript
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;
};
```

### Root.CompleteEventDetails

```typescript
type OTPFieldPreviewRootCompleteEventDetails =
  | { reason: 'input-change'; event: InputEvent | Event }
  | { reason: 'input-paste'; event: ClipboardEvent }
  | { reason: 'keyboard'; event: KeyboardEvent };
```

### Root.CompleteEventReason

```typescript
type OTPFieldPreviewRootCompleteEventReason = 'input-change' | 'input-paste' | 'keyboard';
```

### Root.InvalidEventDetails

```typescript
type OTPFieldPreviewRootInvalidEventDetails =
  | { reason: 'input-change'; event: InputEvent | Event }
  | { reason: 'input-paste'; event: ClipboardEvent };
```

### Root.InvalidEventReason

```typescript
type OTPFieldPreviewRootInvalidEventReason = 'input-change' | 'input-paste';
```

### Root.ValidationType

```typescript
type OTPFieldPreviewRootValidationType = 'numeric' | 'alpha' | 'alphanumeric' | 'none';
```

### Input

An individual OTP character input.
Renders an `<input>` element.

**Input Props:**

| Prop      | Type                                                                                       | Default | Description                                                                                                                                                                                   |
| :-------- | :----------------------------------------------------------------------------------------- | :------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| className | `string \| ((state: OTPFieldInputState) => string \| undefined)`                           | -       | CSS class applied to the element, or a function that&#xA;returns a class based on the component's state.                                                                                      |
| style     | `React.CSSProperties \| ((state: OTPFieldInputState) => React.CSSProperties \| undefined)` | -       | Style applied to the element, or a function that&#xA;returns a style object based on the component's state.                                                                                   |
| render    | `ReactElement \| ((props: HTMLProps, state: OTPFieldInputState) => ReactElement)`          | -       | Allows you to replace the component's HTML element&#xA;with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |

**Input Data Attributes:**

| Attribute     | Type | 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.                                 |

### Input.Props

Re-export of [Input](/react/components/otp-field.md) props.

### Input.State

```typescript
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.

**Separator Props:**

| Prop        | Type                                                                                   | Default        | Description                                                                                                                                                                                   |
| :---------- | :------------------------------------------------------------------------------------- | :------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| orientation | `Orientation`                                                                          | `'horizontal'` | The orientation of the separator.                                                                                                                                                             |
| className   | `string \| ((state: SeparatorState) => string \| undefined)`                           | -              | CSS class applied to the element, or a function that&#xA;returns a class based on the component's state.                                                                                      |
| style       | `React.CSSProperties \| ((state: SeparatorState) => React.CSSProperties \| undefined)` | -              | Style applied to the element, or a function that&#xA;returns a style object based on the component's state.                                                                                   |
| render      | `ReactElement \| ((props: HTMLProps, state: SeparatorState) => ReactElement)`          | -              | Allows you to replace the component's HTML element&#xA;with a different tag, or compose it with another component. Accepts a `ReactElement` or a function that returns the element to render. |

### Separator.Props

Re-export of [Separator](/react/components/otp-field.md) props.

### Separator.State

```typescript
type OTPFieldPreviewSeparatorState = {
  /** The orientation of the separator. */
  orientation: Orientation;
};
```

## Additional Types

### OTPFieldInputProps

```typescript
type OTPFieldInputProps = {
  /**
   * CSS class applied to the element, or a function that
   * returns a class based on the component's state.
   */
  className?: string | ((state: OTPFieldInputState) => string | undefined);
  /**
   * Allows you to replace the component's HTML element
   * with a different tag, or compose it with another component.
   *
   * Accepts a `ReactElement` or a function that returns the element to render.
   */
  render?: ReactElement | ((props: HTMLProps, state: OTPFieldInputState) => ReactElement);
  /**
   * Style applied to the element, or a function that
   * returns a style object based on the component's state.
   */
  style?: React.CSSProperties | ((state: OTPFieldInputState) => React.CSSProperties | undefined);
};
```

### OTPFieldInputState

```typescript
type OTPFieldInputState = {
  /** 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;
};
```

### OTPFieldRootChangeEventReason

```typescript
type OTPFieldRootChangeEventReason = 'input-change' | 'input-clear' | 'input-paste' | 'keyboard';
```

### OTPFieldRootCompleteEventReason

```typescript
type OTPFieldRootCompleteEventReason = 'input-change' | 'input-paste' | 'keyboard';
```

### OTPFieldRootInvalidEventReason

```typescript
type OTPFieldRootInvalidEventReason = 'input-change' | 'input-paste';
```

### OTPFieldRootProps

```typescript
type OTPFieldRootProps = {
  /**
   * The id of the first input element.
   * Subsequent inputs derive their ids from it (`{id}-2`, `{id}-3`, and so on).
   */
  id?: string;
  /**
   * The input autocomplete attribute. Applied to the first slot and hidden validation input.
   * @default 'one-time-code'
   */
  autoComplete?: string;
  /**
   * A string specifying the `form` element with which the hidden input is associated.
   * This string's value must match the id of a `form` element in the same document.
   */
  form?: string;
  /**
   * The number of OTP input slots.
   * Required so the root can clamp values, detect completion, and generate
   * consistent validation markup before all slots hydrate.
   */
  length: number;
  /**
   * Whether to submit the owning form when the OTP becomes complete.
   * @default false
   */
  autoSubmit?: boolean;
  /**
   * Whether the slot inputs should mask entered characters.
   * Pass `type` directly to individual `<OTPField.Input>` parts to use a custom
   * input type.
   * @default false
   */
  mask?: boolean;
  /**
   * 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.
   */
  inputMode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
  /**
   * The type of input validation to apply to the OTP value.
   * @default 'numeric'
   */
  validationType?: OTPFieldRoot.ValidationType;
  /**
   * Function for custom sanitization when `validationType` is set to `'none'`.
   * This function runs before updating the OTP value from user interactions.
   */
  sanitizeValue?: (value: string) => string;
  /**
   * Whether the user must enter a value before submitting a form.
   * @default false
   */
  required?: boolean;
  /**
   * Whether the component should ignore user interaction.
   * @default false
   */
  disabled?: boolean;
  /**
   * Whether the user should be unable to change the field value.
   * @default false
   */
  readOnly?: boolean;
  /** Identifies the field when a form is submitted. */
  name?: string;
  /** The OTP value. */
  value?: string;
  /** The uncontrolled OTP value when the component is initially rendered. */
  defaultValue?: string;
  /**
   * Callback fired when the OTP value changes.
   *
   * The `eventDetails.reason` indicates 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
   */
  onValueChange?: (value: string, eventDetails: OTPFieldRoot.ChangeEventDetails) => void;
  /**
   * Callback fired when entered text contains characters that are rejected by sanitization,
   * before the OTP value updates.
   *
   * The `value` argument is the attempted user-entered string before sanitization.
   */
  onValueInvalid?: (value: string, eventDetails: OTPFieldRoot.InvalidEventDetails) => void;
  /**
   * Callback function that is fired when the OTP value becomes complete.
   *
   * It runs later than `onValueChange`, after the internal value update is applied.
   *
   * If `autoSubmit` is enabled, it runs immediately before the owning form is submitted.
   */
  onValueComplete?: (value: string, eventDetails: OTPFieldRoot.CompleteEventDetails) => void;
  /**
   * CSS class applied to the element, or a function that
   * returns a class based on the component's state.
   */
  className?: string | ((state: OTPFieldRootState) => string | undefined);
  /**
   * Style applied to the element, or a function that
   * returns a style object based on the component's state.
   */
  style?: React.CSSProperties | ((state: OTPFieldRootState) => React.CSSProperties | undefined);
  /**
   * Allows you to replace the component's HTML element
   * with a different tag, or compose it with another component.
   *
   * Accepts a `ReactElement` or a function that returns the element to render.
   */
  render?: ReactElement | ((props: HTMLProps, state: OTPFieldRootState) => ReactElement);
};
```

### OTPFieldRootState

```typescript
type OTPFieldRootState = {
  /** 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;
};
```

## External Types

### ValidationType

```typescript
type ValidationType = 'numeric' | 'alpha' | 'alphanumeric' | 'none';
```

### Orientation

```typescript
type Orientation = 'horizontal' | 'vertical';
```

## Export Groups

- `OTPFieldPreview.Root`: `OTPFieldPreview.Root`, `OTPFieldPreview.Root.State`, `OTPFieldPreview.Root.Props`, `OTPFieldPreview.Root.ValidationType`, `OTPFieldPreview.Root.ChangeEventReason`, `OTPFieldPreview.Root.ChangeEventDetails`, `OTPFieldPreview.Root.InvalidEventReason`, `OTPFieldPreview.Root.InvalidEventDetails`, `OTPFieldPreview.Root.CompleteEventReason`, `OTPFieldPreview.Root.CompleteEventDetails`
- `OTPFieldPreview.Input`: `OTPFieldPreview.Input`, `OTPFieldPreview.Input.State`, `OTPFieldPreview.Input.Props`
- `OTPFieldPreview.Separator`: `OTPFieldPreview.Separator`, `OTPFieldPreview.Separator.Props`, `OTPFieldPreview.Separator.State`
- `Default`: `OTPFieldRootProps`, `OTPFieldRootState`, `OTPFieldRootChangeEventReason`, `OTPFieldRootChangeEventDetails`, `OTPFieldRootInvalidEventReason`, `OTPFieldRootInvalidEventDetails`, `OTPFieldRootCompleteEventReason`, `OTPFieldRootCompleteEventDetails`, `OTPFieldInputState`, `OTPFieldInputProps`

## Canonical Types

Maps `Canonical`: `Alias` — Use Canonical when its namespace is already imported; otherwise use Alias.

- `OTPFieldRoot.ChangeEventDetails`: `OTPFieldRootChangeEventDetails`
- `OTPFieldRoot.InvalidEventDetails`: `OTPFieldRootInvalidEventDetails`
- `OTPFieldRoot.CompleteEventDetails`: `OTPFieldRootCompleteEventDetails`
