One-Time Password Field

One-Time Password Field

일회용 비밀번호(OTP) 검증을 위한 단일 문자 텍스트 입력 그룹 컴포넌트예요.

출처: 문서

본문

문자마다 하나의 입력 칸을 두어 일회용 비밀번호(OTP/인증 코드)를 입력받는 컴포넌트예요. 단일 입력 필드처럼 동작하는 키보드 내비게이션, 붙여넣기 시 값 덮어쓰기, 비밀번호 관리자 자동완성, 숫자/영숫자 검증, 입력 완료 시 자동 제출, 폼 데이터를 위한 숨김 입력을 지원해요. (불안정 API로 unstable_ 접두사 사용)

Features

  • 단일 입력 필드처럼 동작하는 키보드 내비게이션.
  • 붙여넣기 시 값 덮어쓰기.
  • 비밀번호 관리자 자동완성 지원.
  • 숫자/영숫자 값 입력 검증.
  • 완료 시 자동 제출.
  • 폼 데이터에 단일 값을 제공하는 숨김 입력.

Anatomy

모든 파트를 임포트해 조립해요.

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

Root

일회용 비밀번호 필드의 모든 파트를 담아요.

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

값의 단일 문자를 나타내는 텍스트 입력을 렌더링해요.

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

Prop Type Default
asChild boolean false

Examples

기본 사용법

// 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>

세그먼트 컨트롤

Root 컴포넌트는 임의의 children을 받아들이므로, 입력 사이에 구분자를 렌더링하면 시각적으로 분리된 목록을 쉽게 만들 수 있어요. 보조 기술에는 aria-hidden으로 장식 요소를 숨기는 것을 권장하며, 각 자식 요소가 group role의 부모에 속한다고 기대되므로 Root 안에 다른 의미 있는 콘텐츠를 렌더링하지 않는 것이 좋아요.

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

비밀번호 입력 시 폼 자동 제출

autoSubmit prop으로 모든 입력이 채워졌을 때 연결된 폼을 제출할 수 있어요.

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>
  );
}

제어 값

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

현재 WCAG 지침에는 일회용 비밀번호 필드를 별도의 입력으로 구현하는 확립된 단일 패턴이 없어요. 이 동작은 필드가 단일 입력처럼 동작하도록 최대한 가깝게 만드는 것을 목표로 하며, 초기 연구·테스트·피드백 수집을 바탕으로 사용자 기대에 맞는 몇 가지 예외가 있어요.

이 컴포넌트는 group role의 컨테이너 안에 input 요소들로 구현되어, 자식 입력들이 관련되어 있음을 나타내요. 방향 키로 입력을 탐색·포커스할 수 있고, 타이핑하면 마지막 입력에 도달할 때까지 다음 입력으로 포커스가 이동해요.

필드에 값을 붙여넣으면 현재 포커스된 입력과 무관하게 모든 입력의 내용이 교체돼요. 연구에 따르면 보통 비밀번호 관리자나 이메일에서 값을 붙여넣는 사용자 기대와 대체로 일치해요.

키보드 상호작용

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

더 알아보기 (Learn more)

  • 문자의 개수만큼 OneTimePasswordField.Input을 렌더링하고, 폼 값 전달을 위해 HiddenInput을 포함해야 해요.
  • 붙여넣기 시 전체 값이 모든 입력에 채워지고, 완료 시 autoSubmit으로 폼 제출을 시도할 수 있어요.