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으로 폼 제출을 시도할 수 있어요.