OtpInput
Mẫu chuẩnMộ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.
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.
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 cho | Dùng thay |
|---|---|
| Mã có chữ cái — mã mời, mã khuyến mại, mã tham chiếu | Field — 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ẩu | PasswordField |
| Mã dài, dán từ chỗ khác, như khoá hồi phục | Textarea hoặc Input trần — giới hạn maxLength ở đây sẽ cắt |
| Số điện thoại, số tiền, số lượng | Field với type/inputMode phù hợp |
Dựng từ
| Primitive tầng 1 | Vai trong composite |
|---|---|
| Label | Nhã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ần | Ghi chú |
|---|---|---|
| 1 | Label | htmlFor bằng id. |
| 2 | Input | autoComplete, inputMode, pattern, maxLength, chữ căn giữa và giãn. |
| 3 | Ghi 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
Ô trống. autoFocus khi caller yêu cầu.
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.
Mã không đúng hoặc đã hết hạn.
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
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ữu | Cấp qua |
|---|---|
| Giá trị mã | value — controlled, bắt buộc |
| Mỗi lần đổi, sau khi lọc | onChange(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
| Prop | Mặc định | Quy ướ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. |
| length | 6 | Điều khiển đồng thời maxLength và độ dài cắt của bộ lọc. |
| error | undefined | Có giá trị ⇒ aria-invalid="true" cộng role="alert". |
| id | useId() | — |
| autoFocus | undefined | Chỉ 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.