# One-Time Password Field

A group of single-character text inputs to handle one-time password verification.

## Features

- Keyboard navigation mimicking the behavior of a single input field

- Overriding values on paste

- Password manager autofill support

- Input validation for numeric and alphanumeric values

- Auto-submit on completion

- Hidden input to provide a single value to form data

## [Anatomy](#anatomy)

Import all parts and piece them together.

```jsx
import { unstable_OneTimePasswordField as OneTimePasswordField } from "radix-ui";

export default () => (
  <OneTimePasswordField.Root>
    {/* one Input for each character of the value */}

    <OneTimePasswordField.Input />

    {/* single HiddenInput to store the full value */}

    <OneTimePasswordField.HiddenInput />
  </OneTimePasswordField.Root>
);
```

## [API Reference](#api-reference)

### [Root](#root)

Contains all the parts of a one-time password field.

| Prop             | Type       | Default          |
| ---------------- | ---------- | ---------------- |
| `asChild`        | `boolean`  | `false`          |
| `autoComplete`   | `enum`     | `one-time-code`  |
| `autoFocus`      | `boolean`  | No default value |
| `value`          | `string`   | No default value |
| `defaultValue`   | `string`   | No default value |
| `onValueChange`  | `function` | No default value |
| `autoSubmit`     | `boolean`  | `false`          |
| `onAutoSubmit`   | `function` | No default value |
| `disabled`       | `boolean`  | `false`          |
| `dir`            | `enum`     | `"ltr"`          |
| `orientation`    | `enum`     | `"vertical"`     |
| `form`           | `string`   | No default value |
| `name`           | `string`   | No default value |
| `placeholder`    | `string`   | No default value |
| `readOnly`       | `boolean`  | `false`          |
| `sanitizeValue`  | `function` | No default value |
| `type`           | `enum`     | `"text"`         |
| `validationType` | `enum`     | `"numeric"`      |

| Data attribute       | Values                       |
| -------------------- | ---------------------------- |
| `[data-orientation]` | `"vertical" \| "horizontal"` |

### [Input](#input)

Renders a text input representing a single character in the value.

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

| Data attribute | Values                                                                                   |
| -------------- | ---------------------------------------------------------------------------------------- |
| `[data-index]` | The index corresponding with the index of the character relative to the root field value |

### [HiddenInput](#hiddeninput)

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

## [Examples](#examples)

### [Basic usage](#basic-usage)

```jsx
// This will render a field with 6 inputs, for use with

// 6-character passwords. Render an Input component for

// each character of accepted password's length.

<OneTimePasswordField.Root>
  <OneTimePasswordField.Input />

  <OneTimePasswordField.Input />

  <OneTimePasswordField.Input />

  <OneTimePasswordField.Input />

  <OneTimePasswordField.Input />

  <OneTimePasswordField.Input />

  <OneTimePasswordField.HiddenInput />
</OneTimePasswordField.Root>
```

### [Segmented controls](#segmented-controls)

The `Root` component accepts arbitrary children, so rendering a visually segmented list is as simple as rendering separators between inputs. We recommend hiding decorative elements from assistive tech with `aria-hidden` and avoid rendering other meaningful content within `Root` since each child element is expected to belong to the parent with the `group` role.

```jsx
<OneTimePasswordField.Root>
  <OneTimePasswordField.Input />

  <Separator.Root aria-hidden />

  <OneTimePasswordField.Input />

  <Separator.Root aria-hidden />

  <OneTimePasswordField.Input />

  <Separator.Root aria-hidden />

  <OneTimePasswordField.Input />

  <OneTimePasswordField.HiddenInput />
</OneTimePasswordField.Root>
```

### [Auto-submit form when password is entered](#auto-submit-form-when-password-is-entered)

Use the `autoSubmit` prop to submit an associated form when all inputs are filled.

```jsx
function Verify({ validCode }) {
  const PASSWORD_LENGTH = 6;

  function handleSubmit(event) {
    event.preventDefault();

    const formData = event.formData;

    if (formData.get("otp") === validCode) {
      redirect("/authenticated");
    } else {
      window.alert("Invalid code");
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <OneTimePasswordField.Root name="otp" autoSubmit>
        {PASSWORD_LENGTH.map((_, i) => (
          <OneTimePasswordField.Input key={i} />
        ))}

        {/* HiddenInput is required for the form to have data associated with the field */}

        <OneTimePasswordField.HiddenInput />
      </OneTimePasswordField.Root>

      <button>Submit</button>
    </form>
  );
}
```

### [Controlled value](#controlled-value)

```jsx
function Verify({ validCode }) {
  const [value, setValue] = React.useState("");

  const PASSWORD_LENGTH = 6;

  function handleSubmit() {
    if (value === validCode) {
      redirect("/authenticated");
    } else {
      window.alert("Invalid code");
    }
  }

  return (
    <OneTimePasswordField.Root
      autoSubmit
      value={value}
      onAutoSubmit={handleSubmit}
      onValueChange={setValue}
    >
      {PASSWORD_LENGTH.map((_, i) => (
        <OneTimePasswordField.Input key={i} />
      ))}
    </OneTimePasswordField.Root>
  );
}
```

## [Accessibility](#accessibility)

At the time of writing, there is no singular established pattern in WCAG guidelines for implementing one-time password fields as separate inputs. The behavior aims to get as close as possible to having the field act as a single input, with a few exceptions to match user expectations based on our initial research, testing, and gathering feedback.

This component is implemented as `input` elements within a container with a role of `group` to indicate that child inputs are related. Inputs can be navigated and focused using direction keys, and typing input will move focus to the next input until the last input is reached.

Pasting a value into the field will replace the contents of all inputs, regardless of the currently focused input. Based on our research this seems to align with most user expectations, where values are often pasted from password-managers or an email.

### [Keyboard Interactions](#keyboard-interactions)

| Key                   | Description                                                                                    |
| --------------------- | ---------------------------------------------------------------------------------------------- |
| `Enter`               | Attempts to submit an associated `form` if one is found                                        |
| `Tab`                 | Moves focus to the next focusable element outside of the<!-- --> `Root`                        |
| `Shift + Tab`         | Moves focus to the previous focusable element outside of the<!-- --> `Root`                    |
| `ArrowDown`           | Moves focus to the next `Input` when<!-- --> `orientation` is `vertical`.                      |
| `ArrowUp`             | Moves focus to the previous `Input` when<!-- --> `orientation` is `vertical`.                  |
| `ArrowRight`          | Moves focus to the next `Input` when<!-- --> `orientation` is `horizontal`.                    |
| `ArrowLeft`           | Moves focus to the previous `Input` when<!-- --> `orientation` is `horizontal`.                |
| `Home`                | Moves focus to the first `Input`.                                                              |
| `End`                 | Moves focus to the last `Input`.                                                               |
| `Delete`              | Removes the character in the currently focused `Input` and shifts later values back            |
| `Backspace`           | Removes the character in the currently focused `Input` and moves focus to the previous `Input` |
| `Command + Backspace` | Clears the value of all `Input` elements                                                       |

<!--$-->

<!--/$-->
