FormError
Mẫu chuẩnMột băng lỗi ở mức biểu mẫu — không ở mức field — mang role “alert” nên trình đọc màn hình thông báo nó ngay khoảnh khắc nó xuất hiện. Hình học bám ô nhập bên dưới, không bám thẻ.
Playground
Đổi brand, theme và nội dung để xem cùng một FormError thích ứng.
role hiện tại: alert — ngắt để đọc ngay khi node được thêm vào DOM.
import { FormError } from "@nexobox/ui";
{submitFailed ? <FormError>"Sai tài khoản hoặc mật khẩu."</FormError> : null}Bài toán
Khi một lần nộp biểu mẫu thất bại vì lý do không thuộc một field nào — sai thông tin đăng nhập, phiên hết hạn, máy chủ từ chối — thì không có ô nào để gắn lỗi vào. Lỗi ấy phải xuất hiện ở đầu biểu mẫu, phải được thông báo ngay chứ không đợi người dùng tự cuộn lên, và nó phải trông thuộc về biểu mẫu chứ không nổi lên như một thẻ riêng. Một primitive không giải được vì đây là ba quyết định ghép: vai trò aria (alert, không status), hình học bám field (bán kính ô, viền subtle), và một icon cảnh báo cố định. Alert tồn tại nhưng mang hình học của thẻ chứ không của field — xem mục kế tiếp.
Khi nào sử dụng
Nên dùng
Lỗi ở mức biểu mẫu, đặt ngay trước field đầu tiên hoặc ngay trên nút nộp. Lỗi phải được thông báo ngay khi xuất hiện: đăng nhập sai, mã hết hạn, hành động bị từ chối. Một dòng, một câu — composite này không có tiêu đề riêng và không có nút đóng.
Khi nào không dùng
Tránh dùng
| Không dùng cho | Dùng thay |
|---|---|
| Lỗi thuộc một field | Field error hoặc PasswordField error |
| Thông báo thành công | FormNotice — cùng hình học, role="status", icon và màu khác |
| Thông báo có tiêu đề, danh sách, hoặc một nút hành động bên trong | Alert. FormError chỉ nhận một dòng |
| Thông báo tự tắt, nổi ở góc màn hình | Toaster |
| Cảnh báo ambient không do người dùng vừa gây ra — "gói của bạn sắp hết hạn" | Alert với kind="warning". FormError luôn chen ngang trình đọc màn hình; đó là tiếng ồn cho một điều kiện ambient |
Vì sao FormError chưa thu lại thành preset của Alert. Vai trò aria khớp nhau — Alert kind="danger" cũng mặc định role="alert". Viền, bán kính và đệm khác nhau vì một băng nằm trong biểu mẫu nên đồng hình với ô nhập, không với thẻ — vai trò thật. Nhưng FormError còn tự giữ một margin-bottom, và đó không phải vai trò, đó là một khiếm khuyết: một component không nên sở hữu lề ngoài của chính nó. Cách đóng đúng là cho Alert một knob tone="subtle" rồi FormError trở thành một preset mỏng và bỏ lề ngoài — việc đó chạm một primitive đã có consumer nên là quyết định riêng, theo dõi bằng dòng ledger W10.20.
Dựng từ
| Primitive tầng 1 | Vai trong composite |
|---|---|
| — | Không primitive nào. |
| Icon (wrapper, không phải dòng catalogue) | Vẽ AlertCircleIcon, cỡ small, màu status.danger.icon. |
Đây là một trong hai composite trong tầng không dựng từ một primitive tầng 1 nào, và nó vẫn thuộc tầng 2 theo ADR-UI-005 clause 3: nó có thứ nó thêm mà không primitive nào có (hình học bám field, vai trò aria cố định), và nó không gọi tên khái niệm sản phẩm nào.
| FormError thêm gì |
|---|
role="alert" cố định — không nhận từ caller |
| Icon cảnh báo cố định, không chọn được |
| Hình học bám field: bán kính ô, viền bậc subtle, đệm băng |
| Icon căn theo dòng chữ đầu bằng một lề trên nhỏ |
Anatomy
| # | Phần | Ghi chú |
|---|---|---|
| 1 | Icon (AlertCircle) | Cỡ small, màu status.danger.icon, lề trên nhỏ để thẳng dòng chữ đầu. |
| 2 | span (children) | Câu chữ do host cấp. |
Hai phần: icon và chữ. Không tiêu đề, không nút đóng, không vùng hành động.
States
Được render. Trình đọc màn hình thông báo ngay nhờ role="alert".
Không hiện: composite không có chế độ ẩn — caller render nó hoặc không. Hover, focus, disabled: không có. Đây không phải control; không có phần nào nhận tiêu điểm. Không có trạng thái "đóng" — băng biến mất khi nội dung sinh ra nó thay đổi, chứ không vì người dùng bấm vào một dấu nhân.
Bàn phím & trợ năng
Seam — host sở hữu gì
FormError không biết vì sao lần nộp thất bại, không dịch mã lỗi thành câu chữ, không thử lại, và không quyết định khi nào nó nên xuất hiện. Theo ADR-UI-003 clause 2 nó không fetch, không đọc router, không giữ session, không import gì từ next.
| Host sở hữu | Cấp qua |
|---|---|
| Câu chữ của lỗi, bằng ngôn ngữ người đọc | children |
| Khi nào băng tồn tại | Render có điều kiện. Không có prop visible |
| Ánh xạ từ mã lỗi kỹ thuật sang câu người đọc hiểu | Host. Composite không mang một bảng chuỗi nào |
| Lề ngoài của khối, về lâu dài | Hiện composite tự giữ — khiếm khuyết đã ghi ở mục Không dùng cho, dòng ledger W10.20 |
Composite không giữ trạng thái nào. Không useState, không useId.
API & triển khai
| Prop | Mặc định | Quy ước |
|---|---|---|
| children | — | Bắt buộc. Một dòng, một câu. Danh sách prop đóng ở đúng một dòng: không kind, không role, không icon, không className. |
Design tokens
FormError đọc --nf-component-field-radius, --nf-semantic-status-danger-border-subtle, -background, -foreground, -icon, cộng năm alias component.auth.banner* và component.auth.errorMarginBottom cho đệm, khoảng icon–chữ và lề dưới. Lỗ hổng đặt tên, ghi ra thay vì che: năm alias đó gọi tên "auth" trong khi composite này đã được ADR-UI-005 clause 5 xác định là chung — nhóm đúng là component.formBanner.*, theo dõi bằng dòng ledger W10.19. Cỡ chữ 0.8125rem là số trần trong file, không phải token.
Do / Don't
Nên dùng
Render composite sau khi lần nộp thất bại. Gắn lỗi của một field vào chính field đó. Một câu. Dịch mã lỗi ở host.
Tránh dùng
Render sẵn rồi bật/tắt bằng CSS — role="alert" không thông báo một phần tử đã có sẵn từ lúc tải trang. Dồn mọi lỗi lên một băng ở đầu biểu mẫu. Một danh sách lỗi, hay một nút "thử lại" bên trong. Truyền mã lỗi kỹ thuật thẳng vào children. Dùng FormError cho cảnh báo ambient.