Gắn nhãn component
Mỗi control tương tác cần một tên truy cập rõ ràng; cách tốt nhất luôn là nhãn nhìn thấy được, nối với control bằng Field hoặc Label.
Nhãn nhìn thấy: Field và Label
Nhãn nhìn thấy giúp mọi người — người dùng chuột, bàn phím, trình đọc màn hình, nhập bằng giọng nói — cùng biết control dùng để làm gì.
<Field label="Họ và tên" required>
<Input autoComplete="name" />
</Field>
<Checkbox label="Nhận bản tin hằng tháng" />
<Switch label="Chế độ tiết kiệm pin" />
<Slider label="Độ sáng" showValue defaultValue={[60]} />
{/* Bố cục tự do: Label + htmlFor trỏ tới id của control */}
<Label htmlFor="bio" size="large">Giới thiệu</Label>
<Textarea id="bio" rows={3} />
{/* Nhãn kèm giải thích */}
<InfoLabel htmlFor="vat" info="Mã số thuế 10 hoặc 13 chữ số do cơ quan thuế cấp.">Mã số thuế</InfoLabel>
<Input id="vat" inputMode="numeric" />Checkbox, Switch, Radio, Slider có prop label riêng và tự nối bằng useId. Với Input / Textarea / Select / Dropdown / Combobox, bọc trong Field — Field sinh id, gắn htmlFor, aria-describedby và aria-invalid.
Nút chỉ có icon
Icon không có tên. Mọi nút chỉ có icon phải có aria-label; thêm Tooltip cùng nội dung để người dùng nhìn cũng biết.
<div role="toolbar" aria-label="Định dạng văn bản" className="flex gap-1">
<SimpleTooltip content="Đậm (Ctrl+B)">
<Button appearance="subtle" icon={<TextBold20Regular />} aria-label="Đậm" aria-keyshortcuts="Control+B" />
</SimpleTooltip>
<SimpleTooltip content="Nghiêng (Ctrl+I)">
<Button appearance="subtle" icon={<TextItalic20Regular />} aria-label="Nghiêng" aria-keyshortcuts="Control+I" />
</SimpleTooltip>
<SimpleTooltip content="Gạch chân (Ctrl+U)">
<Button appearance="subtle" icon={<TextUnderline20Regular />} aria-label="Gạch chân" aria-keyshortcuts="Control+U" />
</SimpleTooltip>
</div>
{/* Nhãn đủ ngữ cảnh khi có nhiều nút giống nhau trên trang */}
<Button appearance="subtle" icon={<Delete20Regular />} aria-label="Xoá tệp Báo cáo quý 3.pdf" />- Tooltip không thay nhãn: nó chỉ hiện khi hover / focus và Radix gắn nó làm mô tả (
aria-describedby). Tên vẫn phải đến từaria-label. - Nhãn nên khớp chữ trong tooltip (WCAG 2.5.3 — nhãn trong tên) để người dùng điều khiển bằng giọng nói gọi đúng tên.
- Khi một trang có nhiều nút cùng icon (Xoá trên mỗi hàng), thêm ngữ cảnh vào nhãn: “Xoá tệp Báo cáo quý 3.pdf”.
Mô tả & validation: aria-describedby
Tên cho biết control là gì; mô tả cho biết thêm điều cần biết. Field nối cả hint lẫn validationMessage vào control.
<Field
label="Email công việc"
hint="Dùng để đăng nhập và nhận hoá đơn."
validationState="error"
validationMessage="Email cần có @ và tên miền, ví dụ an@congty.vn."
required
>
<Input type="email" defaultValue="an.congty.vn" />
</Field><!-- HTML mà Field sinh ra -->
<label for=":r5:">Email công việc<span>*</span></label>
<input
id=":r5:"
required
aria-invalid="true"
aria-describedby=":r5:-message :r5:-hint"
/>
<span id=":r5:-message">Email cần có @ và tên miền, ví dụ an@congty.vn.</span>
<span id=":r5:-hint">Dùng để đăng nhập và nhận hoá đơn.</span>Trình đọc màn hình đọc: “Email công việc, bắt buộc, không hợp lệ, ô nhập — Email cần có @ và tên miền…, Dùng để đăng nhập…”. validationMessage không tự đọc khi vừa xuất hiện; khi người dùng gửi form lỗi, hãy chuyển focus về ô lỗi đầu tiên — nhãn và mô tả lỗi sẽ được đọc cùng nhau.
Nhãn cho nhóm: RadioGroup & nhóm Checkbox
Mỗi lựa chọn có nhãn riêng, nhưng cả nhóm cũng cần một tên — nếu không, người dùng trình đọc màn hình nghe “Tiêu chuẩn” mà không biết đang chọn gì.
{/* RadioGroup: role="radiogroup" lấy tên từ một nhãn nhìn thấy qua aria-labelledby */}
<div className="flex flex-col gap-1">
<Label id="shipping-label" weight="semibold">Phương thức giao hàng</Label>
<RadioGroup aria-labelledby="shipping-label" defaultValue="standard">
<Radio value="standard" label="Tiêu chuẩn (3–5 ngày)" />
<Radio value="express" label="Hoả tốc (trong ngày)" />
</RadioGroup>
</div>
{/* Nhóm Checkbox: fieldset + legend là cách gốc, không cần ARIA */}
<fieldset>
<legend className="pb-1 text-body1 font-semibold">Nhận thông báo qua</legend>
<div className="flex flex-col">
<Checkbox label="Email" defaultChecked />
<Checkbox label="SMS" />
<Checkbox label="Ứng dụng di động" />
</div>
</fieldset>Lưu ý: <label for> của Field chỉ đặt tên được cho phần tử form thật (input, select, textarea, button). Nếu đặt RadioGroup trong Field bằng render-prop, nhóm vẫn cần aria-labelledby trỏ tới một nhãn có id như ví dụ trên.
Placeholder không phải nhãn
Placeholder biến mất khi bắt đầu gõ, có tương phản thấp (foreground-4) và không được mọi trình đọc màn hình đọc như tên.
Gõ một ký tự là mất ngữ cảnh; không biết định dạng mong đợi.
Nhãn luôn hiện; định dạng nằm trong hint; placeholder chỉ là ví dụ.
- Dùng nhãn nhìn thấy cho mọi control; ngắn, cụ thể, viết hoa chữ đầu.
- aria-label cho nút chỉ có icon, kèm Tooltip cùng nội dung.
- Đặt tên cho nhóm (RadioGroup, nhóm Checkbox) bằng aria-labelledby hoặc fieldset + legend.
- Đưa định dạng, ràng buộc vào hint để được nối qua aria-describedby.
- Không dùng placeholder thay nhãn.
- Không lặp loại control trong nhãn (“Nút Lưu”, “Ô nhập tên”) — trình đọc màn hình đã đọc role.
- Không đặt aria-label khác hẳn chữ đang hiển thị.
- Không dựa vào Tooltip làm tên duy nhất.