Bài 27.1: ARIA attributes và roles

Chào mừng các bạn quay trở lại với chuỗi bài viết về Lập trình Web Front-end! Hôm nay, chúng ta sẽ đi sâu vào một chủ đề cực kỳ quan trọng, đôi khi bị bỏ qua nhưng lại là chìa khóa để xây dựng các ứng dụng web thực sự toàn diệndễ tiếp cận cho tất cả mọi người: đó là ARIA attributes và roles.

Trong thế giới số ngày càng phát triển, việc đảm bảo website và ứng dụng web dễ tiếp cận (accessible) cho tất cả mọi người, bất kể khả năng hay thiết bị sử dụng, là cực kỳ quan trọng. Điều này không chỉ là tuân thủ các quy định pháp lý ở nhiều nơi, mà còn là thể hiện sự tôn trọng đối với người dùng và mở rộng đối tượng tiếp cận của bạn. Các công nghệ hỗ trợ (assistive technologies) như trình đọc màn hình (screen readers), bàn phím chuyên dụng, phần mềm nhận dạng giọng nói... dựa vào cấu trúc và ngữ nghĩa của trang web để "hiểu" và "truyền đạt" nội dung cho người dùng.

ARIA (Accessible Rich Internet Applications) là một bộ đặc tả kỹ thuật được phát triển bởi WAI (Web Accessibility Initiative) của W3C. Mục đích của ARIA là cung cấp thêm ngữ nghĩa (semantics) cho các thành phần HTML, đặc biệt là các widget UI phức tạp hoặc nội dung được cập nhật động mà bản thân HTML gốc chưa cung cấp đầy đủ thông tin về vai trò, trạng thái hay thuộc tính cho công nghệ hỗ trợ.

Hiểu đơn giản, ARIA là lớp "ngữ nghĩa bổ sung" giúp bạn mô tả rõ ràng hơn về các thành phần tương tác và cấu trúc trang web của mình, đặc biệt khi bạn sử dụng các phần tử HTML chung chung như <div> hoặc <span> để xây dựng các widget phức tạp.

ARIA Roles: Phần tử này "Là Gì"?

ARIA Roles (vai trò) định nghĩa loại của một thành phần giao diện người dùng (UI element). Chúng nói cho các công nghệ hỗ trợ biết phần tử đó là gì hoặc làm gì. Ví dụ: một phần tử có thể là một nút bấm (button), một thanh trượt (slider), một menu (menu), hay một vùng điều hướng (navigation).

Các vai trò ARIA được chia thành nhiều loại, nhưng phổ biến nhất là:

  1. Landmark Roles: Mô tả cấu trúc chính của trang, giúp người dùng công nghệ hỗ trợ dễ dàng điều hướng đến các khu vực quan trọng.

    • role="banner": Thường dùng cho tiêu đề trang (header).
    • role="navigation": Dùng cho khối các liên kết điều hướng (menu).
    • role="main": Chứa nội dung chính duy nhất của trang.
    • role="contentinfo": Thường dùng cho chân trang (footer).
    • role="search": Chứa chức năng tìm kiếm.
    • role="form": Chứa một nhóm các thành phần form.
    • role="region": Một khu vực nội dung quan trọng có thể được đặt tên (aria-label hoặc aria-labelledby).
    • role="complementary": Nội dung bổ sung nhưng vẫn liên quan đến nội dung chính (ví dụ: sidebar).

    Ví dụ:

    <header role="banner">...</header>
    <nav role="navigation">...</nav>
    <main role="main">...</main>
    <footer role="contentinfo">...</footer>
    

    (Lưu ý: Các thẻ HTML5 ngữ nghĩa như <header>, <nav>, <main>, <footer> đã tự có ngữ nghĩa tương đương các vai trò ARIA này. Việc thêm role vào đây là hơi thừa nếu trình duyệt hỗ trợ tốt HTML5, nhưng đôi khi vẫn được dùng để tương thích ngược hoặc cung cố ngữ nghĩa.)

  2. Widget Roles: Mô tả các thành phần UI tương tác.

    • role="button": Một nút bấm.
    • role="checkbox": Một hộp kiểm.
    • role="radio": Một nút radio.
    • role="slider": Một thanh trượt.
    • role="dialog": Một cửa sổ pop-up tương tác (modal).
    • role="tab": Một thẻ trong giao diện tab.
    • role="tabpanel": Nội dung tương ứng với một thẻ.
    • role="menuitem": Một mục trong menu.

    Ví dụ về việc tạo một nút bấm "giả" bằng div:

    <div role="button" tabindex="0">
        Click Me
    </div>
    

    Giải thích: Thẻ <div> vốn không có ngữ nghĩa của nút bấm. Bằng cách thêm role="button", ta thông báo cho trình đọc màn hình rằng đây là một nút. tabindex="0" giúp phần tử này có thể nhận focus bằng bàn phím, một hành vi cần thiết cho nút bấm. Tuy nhiên, việc này vẫn không tốt bằng sử dụng thẻ <button> gốc!

QUY TẮC ĐẦU TIÊN CỦA ARIA (The First Rule of ARIA)

Đây là quy tắc quan trọng nhất khi sử dụng ARIA:

Nếu có một phần tử HTML ngữ nghĩa phù hợp, hãy sử dụng nó thay vì dùng ARIA để mô phỏng vai trò của phần tử đó.

Tại sao?

  • Các phần tử HTML ngữ nghĩa gốc (như <button>, <input type="checkbox">, <nav>, <main>) đã được các trình duyệt, công nghệ hỗ trợ và hệ điều hành tối ưu hóa sẵn.
  • Chúng đi kèm với hành vi mặc định (ví dụ: <button> có thể được nhấn bằng phím Space hoặc Enter khi focus, <input type="checkbox"> có thể được toggle bằng Space).
  • Chúng có các trạng thái (state) và thuộc tính (property) tích hợp mà ARIA chỉ mô tả chứ không tự động cung cấp hành vi.
  • Sử dụng ARIA để biến <div> thành nút bấm đòi hỏi bạn phải tự thêm JavaScript để xử lý các sự kiện bàn phím (nhấn Enter/Space), quản lý trạng thái (aria-pressed, aria-disabled), v.v. Điều này rất dễ mắc lỗi và tốn công sức.

Ví dụ về Quy tắc đầu tiên:

  • Nên dùng: <button>Click Me</button>
  • Không nên dùng: <div role="button" tabindex="0">Click Me</div> (trừ khi có lý do cực kỳ chính đáng và bạn biết cách thêm đầy đủ hành vi bàn phím và ARIA states).

  • Nên dùng: <nav>...</nav>

  • Không nên dùng: <div role="navigation">...</div>

ARIA được sinh ra để bổ sung ngữ nghĩa cho các thành phần không có ngữ nghĩa tương ứng trong HTML, hoặc để mô tả các trạng thái động mà HTML tĩnh không làm được, chứ không phải để thay thế HTML ngữ nghĩa.

ARIA Attributes: Phần tử này đang ở "Trạng Thái" nào hoặc có "Thuộc tính" gì?

Trong khi ARIA Roles định nghĩa loại của một phần tử, ARIA Attributes (thuộc tính) cung cấp thêm thông tin về trạng thái (state) hoặc thuộc tính (property) của phần tử đó, hoặc mô tả mối quan hệ giữa các phần tử.

Thuộc tính ARIA luôn bắt đầu bằng aria-. Chúng ta có thể nhóm chúng thành hai loại chính:

  1. ARIA States: Mô tả trạng thái hiện tại của một phần tử. Các giá trị của state thường thay đổi động thông qua JavaScript khi người dùng tương tác hoặc trạng thái ứng dụng thay đổi.

    • aria-checked: Trạng thái của checkbox, radio button hoặc toggle button (true, false, mixed).
    • aria-expanded: Chỉ ra một phần tử điều khiển có mở rộng một nhóm các phần tử khác hay không (true, false, undefined nếu không thể mở rộng). Thường dùng cho accordion, menu thả xuống.
    • aria-hidden: Chỉ ra phần tử hoặc nội dung của nó có hiển thị hoặc khả dụng cho người dùng công nghệ hỗ trợ hay không (true, false). Không dùng thay cho CSS display: none; hoặc visibility: hidden; - hãy dùng cả hai để đảm bảo ẩn hoàn toàn.
    • aria-disabled: Chỉ ra phần tử có đang bị vô hiệu hóa hay không (true, false).
    • aria-selected: Chỉ ra một mục trong bộ sưu tập có đang được chọn hay không (ví dụ: tab đang hoạt động, mục trong listbox).

    Ví dụ về một nút toggle (ví dụ: cho accordion header):

    <button aria-expanded="false" aria-controls="section-content">
        Phần 1: Giới thiệu
    </button>
    <div id="section-content" aria-hidden="true">
        Nội dung của phần giới thiệu bị ẩn.
    </div>
    

    Giải thích: Ban đầu, nút có aria-expanded="false" (chưa mở rộng) và nội dung tương ứng có aria-hidden="true" (đang ẩn). Khi người dùng nhấn nút, JavaScript sẽ thay đổi aria-expanded thành truearia-hidden thành false, thông báo cho trình đọc màn hình rằng nội dung đã hiển thị. aria-controls (một property) liên kết nút với nội dung mà nó điều khiển.

  2. ARIA Properties: Cung cấp thêm thông tin về ngữ nghĩa hoặc mối quan hệ của phần tử. Giá trị của property thường ít thay đổi hơn so với state.

    • aria-label: Cung cấp một nhãn văn bản có thể truy cập được khi không có nhãn trực quan nào khác. Rất hữu ích cho các nút chỉ có biểu tượng.
    • aria-labelledby: Chỉ ra ID của phần tử (hoặc nhiều phần tử) có chứa nhãn cho phần tử hiện tại. Sử dụng khi nhãn đã tồn tại ở nơi khác trên trang.
    • aria-describedby: Chỉ ra ID của phần tử (hoặc nhiều phần tử) có chứa mô tả cho phần tử hiện tại. Cung cấp thông tin bổ sung ngoài nhãn.
    • aria-controls: Chỉ ra ID của phần tử (hoặc nhiều phần tử) mà phần tử hiện tại điều khiển.
    • aria-haspopup: Chỉ ra phần tử có điều khiển một menu pop-up hay loại pop-up khác không (true, menu, listbox, dialog, grid, tree).
    • aria-live: Chỉ ra liệu một vùng nội dung có nên được các công nghệ hỗ trợ thông báo tự động khi nội dung của nó thay đổi hay không.
      • polite: Thông báo khi công nghệ hỗ trợ rảnh rỗi (phù hợp cho thông báo thành công, cập nhật không khẩn cấp).
      • assertive: Thông báo ngay lập tức (phù hợp cho thông báo lỗi khẩn cấp).

    Ví dụ về aria-label cho nút chỉ có biểu tượng:

    <button aria-label="Close dialog"><!-- Biểu tượng dấu X -->
    </button>
    

    Giải thích: Nút này không có văn bản hiển thị, chỉ có biểu tượng '✕'. aria-label="Close dialog" cung cấp một nhãn ngữ nghĩa cho trình đọc màn hình biết chức năng của nút là gì.

    Ví dụ về aria-labelledbyaria-describedby:

    <div>
        <label id="billingLabel">Địa chỉ thanh toán</label>
        <input type="text" aria-labelledby="billingLabel" aria-describedby="billingDescription">
        <p id="billingDescription">Nhập địa chỉ giống với địa chỉ trên thẻ tín dụng của bạn.</p>
    </div>
    

    Giải thích: aria-labelledby="billingLabel" kết nối <input> với <label> có ID là billingLabel, đảm bảo nhãn "Địa chỉ thanh toán" được đọc cho input. aria-describedby="billingDescription" cung cấp mô tả bổ sung từ đoạn <p> có ID billingDescription.

    Ví dụ về aria-live:

    <div role="status" aria-live="polite">
        <!-- Nội dung ở đây sẽ được cập nhật động -->
    </div>
    
    <button onclick="updateStatus()">Cập nhật trạng thái</button>
    
    <script>
        function updateStatus() {
            const statusDiv = document.querySelector('[aria-live="polite"]');
            statusDiv.textContent = "Đang xử lý...";
            setTimeout(() => {
                statusDiv.textContent = "Thao tác hoàn thành!";
            }, 2000);
        }
    </script>
    

    Giải thích: Thẻ <div> với aria-live="polite" là một "live region". Khi nội dung văn bản bên trong nó thay đổi (ví dụ: từ "Đang xử lý..." sang "Thao tác hoàn thành!"), trình đọc màn hình sẽ thông báo sự thay đổi này một cách lịch sự (không ngắt lời người dùng ngay lập tức trừ khi họ đang im lặng). role="status" là một shortcut cho aria-live="polite" cho các thông báo trạng thái không khẩn cấp.

Kết hợp Roles và Attributes: Xây dựng các Widget Phức tạp

Sức mạnh thực sự của ARIA nằm ở việc kết hợp các roles và attributes để mô tả đầy đủ các widget UI phức tạp mà HTML gốc không thể hoặc khó mô tả.

Ví dụ: Một Tab Interface đơn giản

<!-- Các nút Tab -->
<div role="tablist">
    <button role="tab" aria-selected="true" aria-controls="panel1" id="tab1">
        Tab 1
    </button>
    <button role="tab" aria-selected="false" aria-controls="panel2" id="tab2">
        Tab 2
    </button>
</div>

<!-- Các nội dung Panel -->
<div role="tabpanel" id="panel1" aria-labelledby="tab1">
    Nội dung của Tab 1 đang hiển thị.
</div>
<div role="tabpanel" id="panel2" aria-labelledby="tab2" hidden>
    Nội dung của Tab 2 bị ẩn.
</div>

Giải thích:

  • role="tablist": Nhóm các nút tab lại với nhau.
  • role="tab": Mỗi nút đại diện cho một tab.
  • aria-selected="true/false": Cho biết tab nào đang được chọn. JavaScript sẽ cập nhật thuộc tính này khi người dùng chuyển tab.
  • aria-controls="panelX": Liên kết nút tab với nội dung panel mà nó điều khiển (dựa trên ID của panel).
  • role="tabpanel": Mỗi div chứa nội dung tương ứng với một tab.
  • aria-labelledby="tabX": Liên kết nội dung panel với nút tab tương ứng (dựa trên ID của nút tab), cung cấp nhãn ngữ nghĩa cho panel.
  • Thuộc tính hidden hoặc display: none trong CSS được dùng để ẩn/hiện nội dung panel, cùng với việc cập nhật aria-hidden nếu cần (mặc dù hidden tự động có ngữ nghĩa ẩn).

Trong ví dụ này, JavaScript sẽ cần lắng nghe các sự kiện click vào nút tab, cập nhật aria-selected và thuộc tính hidden (hoặc CSS display) của các panel, đồng thời quản lý focus bằng bàn phím để người dùng có thể điều hướng giữa các tab bằng phím mũi tên (một hành vi chuẩn của giao diện tab). Đây là lý do việc xây dựng các widget ARIA phức tạp đòi hỏi sự hiểu biết kỹ lưỡng và code JavaScript đi kèm.

Một số Lưu ý Quan trọng khi sử dụng ARIA

  • Không lạm dụng ARIA: Chỉ sử dụng khi thực sự cần thiết, khi HTML ngữ nghĩa không đủ. Sử dụng ARIA sai cách còn tệ hơn là không dùng gì cả.
  • ARIA không thay đổi giao diện hoặc hành vi: ARIA chỉ thay đổi thông tin được cung cấp cho công nghệ hỗ trợ. Bạn vẫn cần CSS để tạo kiểu và JavaScript để thêm hành vi tương tác (như focus bàn phím, xử lý sự kiện).
  • Luôn kiểm tra với công nghệ hỗ trợ thực tế: Trình giả lập hoặc các công cụ kiểm tra tự động chỉ phát hiện được một phần lỗi. Cách tốt nhất để đảm bảo tính dễ tiếp cận là tự mình kiểm tra bằng các trình đọc màn hình phổ biến (NVDA, JAWS trên Windows; VoiceOver trên macOS/iOS) và chỉ sử dụng bàn phím.
  • Hiểu rõ vai trò và thuộc tính: Mỗi role và attribute có mục đích cụ thể. Hãy đọc tài liệu WAI-ARIA hoặc các nguồn đáng tin cậy khác để sử dụng đúng.

Comments

There are no comments at the moment.