Bỏ qua tới nội dung
Component / 037 / NEXOFRAME

OtpInput

Mẫu chuẩn

Một ô nhập mã một lần — không phải sáu ô một ký tự. Autocomplete và tự điền của hệ điều hành đều nhắm vào một trường, và một trường là ít việc hơn để làm sai cho trình đọc màn hình.

Composite tầng 2API đóng, 7 propLọc chỉ giữ chữ sốDựng từ Label + Input

Playground

Gõ hoặc dán cả chữ và số vào ô — chỉ chữ số còn lại. Đổi length và error để xem OtpInput thích ứng.

LIVE PREVIEW

Giá trị đã lọc: (rỗng) — 0/6

import { OtpInput } from "@nexobox/ui";

function Example() {
  const [value, setValue] = useState("");
  return (
    <OtpInput
      label="Mã xác thực"
      value={value}
      onChange={setValue}
      length={6}
    />
  );
}

Bài toán

Người dùng nhận một mã sáu chữ số và phải đưa nó vào ứng dụng, và việc đó thành công hay thất bại gần như hoàn toàn nhờ tự điền: nếu hệ điều hành đề nghị mã ngay trên bàn phím thì thao tác còn một lần chạm. Một Input trần không đủ vì tự điền chỉ kích hoạt khi có đúng tổ hợp autoComplete="one-time-code", inputMode="numeric" và pattern; và một mã dán từ bộ nhớ tạm thường mang theo khoảng trắng hoặc dấu gạch, nên cần một bộ lọc. Ba thuộc tính cộng một bộ lọc cộng một maxLength suy từ độ dài mã — năm quyết định không thuộc về Input, và gom chúng lại chính là composite này. Một Field cũng không làm được: Field truyền thẳng props và không lọc giá trị.

Khi nào sử dụng

Nên dùng

Nhập mã xác thực một lần: xác minh email, MFA, xác nhận thiết bị. Mã có độ dài biết trước, mặc định sáu chữ số, đổi được bằng length. Bề mặt di động, nơi tự điền là khác biệt lớn nhất giữa dùng được và không.

Khi nào không dùng

Tránh dùng

Không dùng choDùng thay
Mã có chữ cái — mã mời, mã khuyến mại, mã tham chiếuField — composite này lọc bỏ mọi ký tự không phải chữ số, nên sẽ âm thầm ăn mất chữ cái
Mật khẩuPasswordField
Mã dài, dán từ chỗ khác, như khoá hồi phụcTextarea hoặc Input trần — giới hạn maxLength ở đây sẽ cắt
Số điện thoại, số tiền, số lượngField với type/inputMode phù hợp

Dựng từ

Primitive tầng 1Vai trong composite
LabelNhãn, htmlFor bằng id đã giải quyết.
InputÔ nhập duy nhất, mang toàn bộ bộ thuộc tính tự điền.
OtpInput thêm gì mà không primitive nào có
Bộ ba thuộc tính tự điền: autoComplete="one-time-code", inputMode="numeric", pattern="[0-9]*"
maxLength suy từ length — một nguồn, không hai con số phải khớp tay
Bộ lọc onChange: bỏ mọi ký tự không phải chữ số rồi cắt về length. Chạy trên cả gõ tay và dán
Chữ căn giữa, giãn, dùng họ chữ dữ liệu — để mắt đọc nó như một mã
role="alert" trên dòng lỗi, khác Field

Anatomy

#PhầnGhi chú
1LabelhtmlFor bằng id.
2InputautoComplete, inputMode, pattern, maxLength, chữ căn giữa và giãn.
3Ghi chú (p)role="alert", chỉ render khi có error.

Không có ô thứ hai, không có dấu phân tách, không có ô trống đếm số ký tự còn lại. Một ô, và chữ giãn làm việc mà sáu hộp định làm.

States

RỖNG

Ô trống. autoFocus khi caller yêu cầu.

ĐANG NHẬP

0 < value.length < length. Ký tự khác không bao giờ xuất hiện vì bị lọc trước khi vào state.

LỖI

role="alert" -- lỗi xuất hiện sau một lần nộp, người dùng đang chờ kết quả.

Đủ độ dài: ô không tự nộp — việc nộp là của host. Hover, focus, disabled: kế thừa từ Input. Unknown — không có trạng thái loading: trong lúc host đang xác thực mã, việc khoá ô hay hiện chỉ báo do host làm.

Bàn phím & trợ năng

Tab vào ô, Tab ra -- đến từ Input.
Gõ chữ số, Backspace, chọn toàn bộ, dán -- qua Input, nhưng giá trị đi qua bộ lọc của composite trước khi thành state.
Bàn phím số trên di động -- OtpInput thêm, qua inputMode="numeric".
Đề nghị tự điền mã trên thanh bàn phím -- OtpInput thêm, qua autoComplete="one-time-code".
Composite không thêm ArrowLeft/ArrowRight nhảy giữa các hộp, không tự nhảy hộp khi gõ, không Backspace nhảy về hộp trước -- cả lớp hành vi đó là thứ quyết định một-ô giúp tránh.

Seam — host sở hữu gì

OtpInput không xác thực mã, không gọi gì, không tự nộp khi đủ số ký tự, và không đếm số lần thử. Theo ADR-UI-003 clause 2 nó không fetch, không đọc router, không giữ session, không import gì từ next. Clause 6: không mảnh nào của phần bảo mật mô phỏng trong bộ kit được port, nên composite này không biết mã nào là đúng và không được dạy để biết.

Host sở hữuCấp qua
Giá trị mãvalue — controlled, bắt buộc
Mỗi lần đổi, sau khi lọconChange(value: string)
Độ dài mãlength, mặc định 6
Quyết định nộp — kể cả nộp tự động khi đủ ký tựonChange ở host, so value.length === length
Kết quả xác thực, và câu chữ của nóerror
Giới hạn số lần thử, cooldown, gửi lại mãHost

API & triển khai

PropMặc địnhQuy ước
label—Bắt buộc.
value—Bắt buộc. Luôn controlled.
onChange—Bắt buộc. Nhận chuỗi đã lọc và đã cắt, không nhận event.
length6Điều khiển đồng thời maxLength và độ dài cắt của bộ lọc.
errorundefinedCó giá trị ⇒ aria-invalid="true" cộng role="alert".
iduseId()—
autoFocusundefinedChỉ bật trên trang mà ô này là việc duy nhất.

API này không mở rộng InputProps, khác Field. Danh sách prop đóng ở đúng bảy dòng — một type hay inputMode truyền từ ngoài sẽ phá đúng bộ thuộc tính khiến tự điền hoạt động.

Design tokens

OtpInput đọc --nf-component-auth-field-gap, --nf-foundation-typography-data-font-family, --nf-foundation-typography-caption-font-size, -caption-line-height, --nf-semantic-status-danger-foreground. Hai giá trị chưa thành token, ghi ra thay vì che: letter-spacing: 0.5em và cỡ chữ 1.25rem của dãy số là số trần trong file — corpus chưa có vai "chữ mã" nên chưa có alias.

Do / Don't

Nên dùng

Giữ đúng một ô. Dùng cho mã chỉ chữ số. Bật autoFocus khi trang chỉ có mỗi việc nhập mã. Để host quyết định khi nào nộp. Truyền error sau mỗi lần thử thất bại. Đặt length một chỗ.

Tránh dùng

Đổi sang sáu hộp một ký tự "cho giống bộ kit". Dùng cho mã mời có chữ cái. Bật autoFocus trên một trang có nội dung phía trên. Trông đợi composite tự nộp khi đủ sáu số. Chỉ đổi màu ô mà không truyền error.