Thành phần web dễ tiếp cận

Ranh giới bóng thay đổi cách hoạt động của ngữ nghĩa và ARIA. Có hai lỗi thường gặp: thiếu ngữ nghĩa bên trong cây bóng và các tham chiếu ARIA bị hỏng mà không có thông báo vì chúng không thể vượt qua gốc bóng.

Bắt đầu từ mã đánh dấu ngữ nghĩa: sử dụng <button>, <nav>, <ul> thực bên trong mẫu để hầu hết ngữ nghĩa đều miễn phí và dành riêng ElementInternals/ARIA cho các khoảng trống.

Ngữ nghĩa mặc định thông qua mixin ARIA ElementInternals

Đặt vai trò và trạng thái ARIA của phần tử tuỳ chỉnh trong JS thông qua ElementInternals, thay vì ghi các thuộc tính ARIA vào máy chủ lưu trữ. Điều này giúp giữ ngữ nghĩa làm mặc định mà thành phần sở hữu, trong khi vẫn cho phép người dùng ghi đè chúng bằng các thuộc tính trong Light DOM.

class ToggleSwitch extends HTMLElement {
  #internals;
  constructor() {
    super();
    this.#internals = this.attachInternals();
    this.#internals.role = 'switch';          // default role, no host attribute needed
    this.#internals.ariaChecked = 'false';    // exposed to the accessibility tree
    this.addEventListener('click', () => this.toggle());
    this.addEventListener('keydown', (e) => {
      if (e.key === ' ') { e.preventDefault(); this.toggle(); }
    });
  }
  connectedCallback() {
    // Put the host in the tab order on connect (setting tabIndex in the constructor throws on createElement).
    if (!this.hasAttribute('tabindex')) this.tabIndex = 0;
  }
  toggle() {
    const on = this.#internals.ariaChecked !== 'true';
    this.#internals.ariaChecked = String(on);
  }
}
customElements.define('toggle-switch', ToggleSwitch);

Điều này giúp tránh làm lộn xộn DOM bằng các thuộc tính ARIA và ngăn người dùng vô tình làm hỏng các thuộc tính đó, trong khi cây hỗ trợ tiếp cận vẫn thấy vai trò và trạng thái chính xác.

Quản lý tiêu điểm và delegatesFocus

  • Khi bản thân phần tử lưu trữ là thành phần điều khiển tương tác và mang vai trò cũng như trạng thái thông qua ElementInternals (như ToggleSwitch ở trên), hãy đặt phần tử lưu trữ theo thứ tự thẻ bên trong connectedCallback (if (!this.hasAttribute('tabindex')) this.tabIndex = 0 — không bao giờ đặt trong hàm khởi tạo, hàm này sẽ gửi đến document.createElement) để tiêu điểm nằm trên phần tử lưu trữ và công nghệ hỗ trợ sẽ thông báo vai trò cũng như trạng thái của phần tử đó. Đừng đặt tabindex trên máy chủ lưu trữ cho các thành phần tĩnh, không tương tác (chẳng hạn như chú thích hoặc thẻ).
  • Đính kèm gốc bóng bằng delegatesFocus: true khi thành phần gói một thành phần điều khiển có thể làm tâm điểm nội bộ sở hữu ngữ nghĩa (hoặc trên một phần tử tuỳ chỉnh liên kết với biểu mẫu bằng static formAssociated = true). Việc lấy tiêu điểm vào máy chủ lưu trữ (và nhấp vào <label> được liên kết cho các phần tử liên kết với biểu mẫu) sau đó sẽ chuyển tiêu điểm đến phần tử con có thể làm tâm điểm đầu tiên và :focus khớp với máy chủ lưu trữ. Đừng kết hợp delegatesFocus: true với một vai trò/trạng thái trên máy chủ lưu trữ, nếu không, tiêu điểm sẽ nằm trên phần tử bên trong và bỏ qua trạng thái của máy chủ lưu trữ.
  • Để di chuyển tiêu điểm hoặc di chuyển tiêu điểm vào một vùng mới xuất hiện, hãy cung cấp tabindex="-1" cho mục tiêu và gọi .focus(); hãy làm rõ các mục tiêu có thể lấy tiêu điểm theo cách có lập trình thay vì dựa vào thứ tự thẻ.

Mối quan hệ ARIA giữa các gốc bóng

  • BẮT BUỘC: một id được tham chiếu bởi aria-labelledby, aria-describedby, aria-controls, v.v. chỉ phân giải trong cây riêng của phần tử tham chiếu. Thuộc tính dựa trên id trong Light DOM không thể trỏ đến id bên trong cây bóng và ngược lại; tham chiếu bị bỏ qua mà không có thông báo.
  • Để vượt qua ranh giới, hãy sử dụng các thuộc tính phần tử ARIA được phản ánh (ví dụ: ariaLabelledByElements, ariaDescribedByElements, v.v.), lấy các tham chiếu phần tử JS trực tiếp thay vì mã nhận dạng. Các mối quan hệ này phân giải ra bên ngoài: một phần tử bên trong cây tối có thể trỏ đến một mục tiêu trong DOM sáng hoặc bất kỳ cây đối tượng cấp trên nào (bao gồm cả nội dung có khe), vì vậy, bạn có thể gắn nhãn cho một thành phần điều khiển bên trong cây tối từ nội dung DOM sáng – điều mà các tham chiếu id không thể thể hiện.
  • Các mối quan hệ này không phân giải vào trong: một phần tử không thể tham chiếu vào cây tối con (ví dụ: một máy chủ lưu trữ không thể được gắn nhãn bằng nội dung tối của chính nó). Đối với hướng đó, cơ chế Mục tiêu tham chiếu mới nổi là hướng tiêu chuẩn, nhưng chưa được cung cấp rộng rãi.
  • Khi không thể thể hiện mối quan hệ theo cách nào, hãy giữ mối quan hệ đó trong một cây (hiển thị nhãn trong cùng một cây) hoặc sử dụng văn bản aria-label thay vì tham chiếu id.

Chiến lược dự phòng

Nếu không có mixin ARIA ElementInternals, hãy đặt vai trò và trạng thái ARIA làm thuộc tính trên máy chủ lưu trữ (this.setAttribute('role', 'switch')). Cách này hoạt động ở mọi nơi, nhưng có thể khiến người dùng ghi đè các thuộc tính trong DOM.

Khi không có các thuộc tính phần tử ARIA được phản ánh, hãy sử dụng thuộc tính aria-* dựa trên id cho các mối quan hệ cùng cây; nhãn xuyên ranh giới không có thuộc tính tương đương dựa trên id, vì vậy, hãy quay lại văn bản aria-label.