Yugen UI

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ì.

Độ sáng60
<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.

Email cần có @ và tên miền, ví dụ an@congty.vn.Dùng để đăng nhập và nhận hoá đơn.
<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ì.

Nhận thông báo qua
{/* 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.

✕ Không nên

Gõ một ký tự là mất ngữ cảnh; không biết định dạng mong đợi.

✓ Nên
10 chữ số, bắt đầu bằng 0.

Nhãn luôn hiện; định dạng nằm trong hint; placeholder chỉ là ví dụ.

Nên
  • 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 nên
  • 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.