Dev Tools & Workflow

Hướng dẫn cho lập trình viên về nhãn ARIA thực sự hữu ích

Nhãn ARIA không phải là một lớp hỗ trợ tiếp cận thần kỳ. Dùng đúng, chúng giúp các điều khiển dễ hiểu hơn. Dùng tùy tiện, chúng che khuất văn bản hữu ích và tạo ra giao diện gây bối rối.

The Wux Webtools Team The Wux Webtools Team 15 phút đọc Hỗ trợ AI, được con người xem xét
Illustration of a developer reviewing accessible labels and UI components on a screen.
Mục lục
  1. Nhãn ARIA dùng để đặt tên, không phải để chữa cháy
  2. Tên hỗ trợ tiếp cận, nói một cách dễ hiểu
  3. Quy tắc đầu tiên: ưu tiên HTML gốc và nhãn hiển thị
  4. Khi `aria-label` là công cụ phù hợp
  5. Khi `aria-label` là công cụ sai
  6. Hãy dùng `aria-labelledby` khi văn bản hiển thị đã tồn tại
  7. Dùng `aria-describedby` cho văn bản trợ giúp, không phải cho tên
  8. Điều khiển lặp lại cần tên riêng biệt
  9. Đừng gắn nhãn cho mọi thứ
  10. Kiểm tra tên được tính toán, không chỉ kiểm tra mã
  11. Checklist review thực tế
  12. Kỷ luật thầm lặng của ARIA tốt

Nhãn ARIA dùng để đặt tên, không phải để chữa cháy

ARIA hữu ích, nhưng thường bị dùng như một miếng vá cho HTML chưa rõ ràng. Đó là lúc các đội ngũ dễ gặp rắc rối.

Ví dụ phổ biến nhất là aria-label. Nó trông vô hại: thêm một chuỗi, làm hài lòng linter, rồi tiếp tục. Nhưng tên hỗ trợ tiếp cận không phải là phần trang trí. Đó là tên mà nhiều công nghệ hỗ trợ dùng để trình bày cho người dùng khi họ điều hướng qua nút, liên kết, trường biểu mẫu, tiêu đề, landmark và điều khiển.

Nếu tên đó mơ hồ, trùng lặp, lỗi thời hoặc khác với nhãn hiển thị, giao diện sẽ khó dùng hơn. Đôi khi còn tệ hơn: aria-label có thể ghi đè văn bản tốt hơn vốn đã có trong DOM.

Mục tiêu không phải là thêm nhiều ARIA hơn. Mục tiêu là làm cho tên, vai trò, trạng thái và mục đích của từng phần tử giao diện trở nên rõ ràng.

Tên hỗ trợ tiếp cận, nói một cách dễ hiểu

Hầu hết các phần tử tương tác đều có tên hỗ trợ tiếp cận. Trình đọc màn hình dùng tên đó để thông báo phần tử là gì.

Ví dụ:

<button>Save changes</button>

Trình đọc màn hình có thể thông báo đại loại: “Save changes, button.” Vai trò đến từ phần tử button gốc. Tên đến từ văn bản bên trong nó.

Đó là trường hợp lý tưởng: văn bản hiển thị và tên hỗ trợ tiếp cận khớp nhau.

Các thuộc tính gắn nhãn ARIA trở nên hữu ích khi giao diện hiển thị không cung cấp một tên đầy đủ, hoặc khi tên phải lấy từ một phần tử khác. Các thuộc tính chính là:

  • aria-label: cung cấp một chuỗi trực tiếp trên phần tử.
  • aria-labelledby: trỏ đến một hoặc nhiều phần tử có văn bản trở thành tên.
  • aria-describedby: trỏ đến văn bản mô tả bổ trợ, không phải tên chính.

Ba thuộc tính này có liên quan, nhưng không thể thay thế cho nhau.

Quy tắc đầu tiên: ưu tiên HTML gốc và nhãn hiển thị

Nếu bạn có thể đặt văn bản hiển thị trên điều khiển, hãy làm việc đó trước.

Cách này tốt hơn:

<button>Delete invoice</button>

So với cách này:

<button aria-label="Delete invoice">
  <svg aria-hidden="true" focusable="false">...</svg>
</button>

Mẫu thứ hai hợp lệ cho một nút chỉ có biểu tượng. Nhưng nếu thiết kế có thể chấp nhận văn bản hiển thị, văn bản hiển thị sẽ giúp tất cả mọi người: người dùng trình đọc màn hình, người dùng nhận dạng giọng nói, người đang chịu tải nhận thức, người quét nhanh nội dung và người dùng công cụ dịch.

Đây là một chủ đề lặp lại trong công việc hỗ trợ tiếp cận. HTML gốc và các dấu hiệu hiển thị giải quyết được nhiều vấn đề hơn siêu dữ liệu ẩn. Nguyên tắc tương tự cũng áp dụng rộng hơn cho ngữ nghĩa của nút; nếu đội ngũ của bạn đang rà soát các điều khiển UI, checklist cho nút web hỗ trợ tiếp cận của chúng tôi là tài liệu đi kèm phù hợp với hướng dẫn này.

Khi aria-label là công cụ phù hợp

Dùng aria-label khi một phần tử cần tên hỗ trợ tiếp cận và không có văn bản hiển thị phù hợp để tham chiếu.

Trường hợp kinh điển là nút chỉ có biểu tượng:

<button aria-label="Search">
  <svg aria-hidden="true" focusable="false" viewBox="0 0 24 24">
    <!-- icon -->
  </svg>
</button>

Cách này hợp lý. Biểu tượng hiển thị gợi ý chức năng tìm kiếm, nhưng đường dẫn SVG tự nó không cung cấp một tên đáng tin cậy. aria-label bổ sung tên đó.

Các trường hợp tốt khác gồm:

  • Nút đóng chỉ được biểu diễn bằng dấu “X”.
  • Landmark điều hướng cần một tên cụ thể hơn, chẳng hạn aria-label="Product".
  • Một điều khiển lặp lại mà ngữ cảnh hiển thị không nằm trong văn bản của nút.

Ví dụ:

<nav aria-label="Primary">
  ...
</nav>

<nav aria-label="Footer">
  ...
</nav>

Cả hai đều là landmark điều hướng, nhưng nhãn của chúng giúp người dùng phân biệt khi di chuyển theo landmark.

Khi aria-label là công cụ sai

Đừng thêm aria-label chỉ vì một bài kiểm tra nói rằng phần tử cần nhãn. Hãy sửa markup trước.

Không tốt:

<div role="button" tabindex="0" aria-label="Submit">Submit</div>

Tốt hơn:

<button>Submit</button>

Ví dụ đầu tiên tạo thêm công việc không cần thiết. Bây giờ bạn phải tái tạo hành vi bàn phím, trạng thái vô hiệu hóa, hành vi biểu mẫu và những kỳ vọng mà nút gốc đã cung cấp sẵn.

Cũng nên tránh dùng aria-label để đổi tên văn bản hiển thị theo cách làm thay đổi ý nghĩa.

<button aria-label="Delete invoice">Remove</button>

Điều này trông nhỏ, nhưng có thể gây bối rối cho người dùng dựa vào nhập liệu bằng giọng nói. Nếu một nút hiển thị ghi “Remove,” nhưng tên hỗ trợ tiếp cận là “Delete invoice,” người dùng cố nói “click Remove” có thể không nhận được kết quả mong đợi. Yêu cầu “label in name” của WCAG tồn tại chính vì lý do này: văn bản hiển thị nói chung nên được chứa trong tên hỗ trợ tiếp cận.

Phiên bản tốt hơn:

<button aria-label="Remove invoice">Remove</button>

Thường còn tốt hơn nữa:

<button>Remove invoice</button>

Hãy dùng aria-labelledby khi văn bản hiển thị đã tồn tại

Nếu văn bản nhãn đã có trên trang, aria-labelledby thường tốt hơn aria-label.

Ví dụ:

<h2 id="billing-title">Billing address</h2>
<section aria-labelledby="billing-title">
  ...
</section>

Tên hỗ trợ tiếp cận của section lúc này đến từ tiêu đề hiển thị. Bạn tránh lặp lại chuỗi, nhờ đó giảm lỗi dịch thuật và nhãn lỗi thời.

Điều này đặc biệt hữu ích cho các nhóm biểu mẫu:

<fieldset aria-labelledby="shipping-speed-title">
  <legend id="shipping-speed-title">Shipping speed</legend>

  <label>
    <input type="radio" name="shipping" value="standard">
    Standard
  </label>

  <label>
    <input type="radio" name="shipping" value="express">
    Express
  </label>
</fieldset>

Trong nhiều trường hợp, legend gốc là đủ mà không cần ARIA. Ý chính là nhãn hiển thị nên dẫn dắt. ARIA nên kết nối ý nghĩa đã có, không tạo ra một phiên bản riêng tư thứ hai của ý nghĩa đó.

Dùng aria-describedby cho văn bản trợ giúp, không phải cho tên

Mô tả không phải là nhãn.

Xem trường này:

<label for="password">Password</label>
<input id="password" type="password" aria-describedby="password-help">
<p id="password-help">Use at least 12 characters.</p>

Tên hỗ trợ tiếp cận là “Password.” Mô tả là “Use at least 12 characters.” Trình đọc màn hình có thể thông báo cả hai, nhưng chúng phục vụ những mục đích khác nhau.

Đừng làm thế này:

<input type="password" aria-label="Use at least 12 characters">

Cách đó đặt tên trường theo hướng dẫn, không phải theo khái niệm. Người dùng điều hướng một biểu mẫu muốn biết trường đó là gì trước, rồi mới đến các ràng buộc áp dụng.

Sự phân biệt này cũng quan trọng trong trạng thái lỗi:

<label for="email">Email</label>
<input
  id="email"
  type="email"
  aria-invalid="true"
  aria-describedby="email-error"
>
<p id="email-error">Enter an email address in the format [email protected].</p>

Nhãn vẫn ổn định. Thông báo lỗi trở thành ngữ cảnh bổ trợ.

Điều khiển lặp lại cần tên riêng biệt

Danh sách và thẻ là nơi nhãn ARIA thường trở nên cần thiết.

Không tốt:

<button>Delete</button>
<button>Delete</button>
<button>Delete</button>

Người dùng trình đọc màn hình điều hướng theo nút có thể nghe “Delete, button” ba lần mà không có ngữ cảnh.

Tốt:

<button aria-label="Delete report: Q4 revenue">Delete</button>
<button aria-label="Delete report: Hiring plan">Delete</button>
<button aria-label="Delete report: Vendor list">Delete</button>

Đây là cách dùng aria-label hợp lệ: văn bản hiển thị vẫn ngắn gọn, còn tên hỗ trợ tiếp cận bao gồm đối tượng.

Nhưng hãy dùng mẫu này cẩn thận. Nếu tên đối tượng hiển thị ở gần đó, aria-labelledby có thể dễ bảo trì hơn:

<article>
  <h3 id="report-q4">Q4 revenue</h3>
  <button aria-labelledby="delete-q4 report-q4" id="delete-q4">Delete</button>
</article>

Tên hỗ trợ tiếp cận trở thành “Delete Q4 revenue.” Cách này tránh lặp lại tiêu đề báo cáo trong một thuộc tính.

Đừng gắn nhãn cho mọi thứ

Không phải phần tử nào cũng cần nhãn ARIA.

Văn bản tĩnh thường không cần. Biểu tượng trang trí không cần. Container không cần, trừ khi chúng có landmark hoặc vai trò widget có ý nghĩa. Gắn nhãn quá mức có thể làm trang trở nên ồn ào và khó điều hướng hơn.

Với hình ảnh, hãy dùng mô hình dành riêng cho hình ảnh: ảnh có ý nghĩa cần alt hữu ích; ảnh trang trí cần alt="" rỗng. Đừng dùng nhãn ARIA để thay thế văn bản ảnh tốt. Nếu đội ngũ của bạn đang trộn lẫn các khái niệm đó, hãy xem lại văn bản alt thực dụng cho hình ảnh và tách phần thay thế hình ảnh khỏi tên điều khiển.

Một lỗi phổ biến là gắn aria-label cho mọi SVG. Nếu SVG nằm bên trong một nút và nút đã có tên, biểu tượng thường nên được ẩn khỏi công nghệ hỗ trợ:

<button aria-label="Open menu">
  <svg aria-hidden="true" focusable="false">...</svg>
</button>

Nếu không, người dùng có thể nghe các thông báo dư thừa hoặc kỳ lạ tùy theo tổ hợp trình duyệt và công nghệ hỗ trợ.

Kiểm tra tên được tính toán, không chỉ kiểm tra mã

Lỗi hỗ trợ tiếp cận thường vượt qua code review vì markup trông có vẻ hợp lý.

Các công cụ dành cho nhà phát triển trong trình duyệt hiện đại có thể hiển thị cây hỗ trợ tiếp cận được tính toán. Trong Chrome, Edge, Firefox và Safari, hãy inspect phần tử và tìm thông tin hỗ trợ tiếp cận như vai trò, tên và mô tả. Bạn đang kiểm tra ba điều:

  1. Vai trò có đúng như bạn mong đợi không?
  2. Tên hỗ trợ tiếp cận có rõ ràng và cụ thể không?
  3. Mô tả có hữu ích mà không thay thế tên không?

Sau đó kiểm thử một vài luồng bằng trình đọc màn hình thật. Bạn không cần trở thành chuyên gia công nghệ hỗ trợ toàn thời gian để bắt được những vấn đề cơ bản. Trên macOS, VoiceOver được tích hợp sẵn. Trên Windows, NVDA được dùng rộng rãi và miễn phí. Trên thiết bị di động, hãy kiểm thử với VoiceOver trên iOS và TalkBack trên Android khi phù hợp.

Công cụ tự động rất hữu ích, nhưng chúng không thể đáng tin cậy cho biết liệu “Open,” “Read more,” hay “Delete” đã đủ ngữ cảnh hay chưa. Hãy xem tự động hóa như một tấm lưới, không phải một vị thẩm phán. Điều này tương tự kiểm tra hiệu năng: một báo cáo có thể chỉ ra các khu vực đáng nghi, nhưng bạn vẫn cần diễn giải tác động. Cách tiếp cận bình tĩnh mà chúng tôi khuyến nghị khi đọc báo cáo Lighthouse mà không hoảng cũng áp dụng ở đây.

Checklist review thực tế

Trước khi phát hành nhãn ARIA, hãy hỏi:

  • Có thể dùng HTML gốc thay cho việc này không?
  • Có văn bản hiển thị nào nên được dùng làm nhãn không?
  • Nếu có văn bản hiển thị, tên hỗ trợ tiếp cận có bao gồm văn bản đó không?
  • Các điều khiển lặp lại có đủ riêng biệt khi được điều hướng ngoài ngữ cảnh trực quan không?
  • Văn bản trợ giúp có được kết nối bằng aria-describedby, thay vì bị nhồi vào nhãn không?
  • Biểu tượng trang trí có được ẩn khỏi công nghệ hỗ trợ không?
  • Đã có ai kiểm tra tên hỗ trợ tiếp cận được tính toán trong công cụ dev của trình duyệt chưa?
  • Đã có ít nhất một lượt kiểm thử bằng trình đọc màn hình thật cho luồng quan trọng chưa?

Checklist này bắt được hầu hết vấn đề về nhãn trước khi chúng trở thành vấn đề của người dùng.

Kỷ luật thầm lặng của ARIA tốt

Làm ARIA tốt hiếm khi kịch tính. Phần lớn là sự tiết chế.

Dùng nút thật. Dùng nhãn thật. Giữ tên hiển thị và tên hỗ trợ tiếp cận thống nhất. Chỉ thêm aria-label khi không có nguồn hiển thị nào tốt hơn. Dùng aria-labelledby khi trang đã có đúng văn bản. Dùng aria-describedby cho hướng dẫn bổ trợ và lỗi.

Nền tảng web cho lập trình viên rất nhiều thứ miễn phí khi chúng ta dùng nó trực tiếp. ARIA tồn tại để lấp các khoảng trống. Kỹ năng nằm ở việc biết khi nào thật sự có khoảng trống.

Câu hỏi thường gặp

Có nên đặt aria-label cho mọi nút không?
Không. Một nút có văn bản hiển thị rõ ràng thường đã có tên hỗ trợ tiếp cận tốt. Chỉ thêm `aria-label` khi văn bản hiển thị bị thiếu hoặc chưa đủ, chẳng hạn nút chỉ có biểu tượng hoặc nút “Delete” lặp lại cần ngữ cảnh.
Sự khác nhau giữa aria-label và aria-labelledby là gì?
`aria-label` cung cấp một chuỗi văn bản trực tiếp trong thuộc tính. `aria-labelledby` trỏ đến văn bản đã có ở nơi khác trên trang. Nếu văn bản hiển thị phù hợp đã tồn tại, `aria-labelledby` thường dễ bảo trì hơn.
aria-label có thể sửa một div được dùng như nút không?
Nó có thể cung cấp tên, nhưng không làm cho phần tử hoạt động như một nút thật. Bạn vẫn cần xử lý hành vi bàn phím, focus, trạng thái và ngữ nghĩa mong đợi. Trong hầu hết trường hợp, hãy dùng `<button>` gốc.
aria-label có nên khớp chính xác với văn bản hiển thị không?
Thông thường nó nên bao gồm văn bản hiển thị, đặc biệt với các điều khiển tương tác. Điều này hỗ trợ người dùng nhận dạng giọng nói và đáp ứng tinh thần của hướng dẫn label-in-name trong WCAG.
Làm sao biết trình đọc màn hình sẽ thông báo gì?
Hãy bắt đầu bằng cách kiểm tra cây hỗ trợ tiếp cận trong công cụ dành cho nhà phát triển của trình duyệt để xem vai trò, tên và mô tả. Sau đó kiểm thử các tương tác quan trọng bằng trình đọc màn hình thật như VoiceOver, NVDA, TalkBack hoặc JAWS.

Nguồn & tài liệu tham khảo thêm

  1. WAI-ARIA Authoring Practices Guide
  2. MDN: aria-label attribute
  3. Accessible Name and Description Computation 1.2
  4. WCAG 2.2 Success Criterion 2.5.3: Label in Name
Về tác giả
The Wux Webtools Team

Cập nhật lần cuối:

Tiếp tục đọc