Bài 27.5: Bài tập thực hành accessibility

Chào mừng bạn đến với bài thực hành chuyên sâu về Accessibility (Khả năng tiếp cận)!

Sau khi đã tìm hiểu về những nguyên tắc và lý do tại sao accessibility lại quan trọng đến vậy (cho cả người dùng, cho SEO, và cho chính chúng ta), giờ là lúc chúng ta đưa lý thuyết vào thực tế. Đây là cơ hội để bạn củng cố kiến thức và biến nó thành kỹ năng thực sự thông qua các ví dụ code cụ thể.

Hãy nhớ: Mục tiêu của accessibility không phải là tạo ra trải nghiệm riêng biệt cho người khuyết tật, mà là xây dựng một website mà ai cũng có thể sử dụng một cách hiệu quả và thoải mái nhất.

Chúng ta sẽ đi qua một số tình huống phổ biến và xem cách chúng ta có thể cải thiện tính accessibility trong từng trường hợp.

1. Sức Mạnh Của HTML Semantics (Ngữ Nghĩa HTML)

Một trong những nền tảng quan trọng nhất của web accessible là sử dụng HTML ngữ nghĩa một cách chính xác. Thay vì chỉ dùng <div> cho mọi thứ, hãy chọn thẻ HTML mô tả đúng nhất nội dung hoặc chức năng của nó.

  • Tại sao? Screen readers và các công nghệ hỗ trợ khác dựa vào ngữ nghĩa của thẻ HTML để hiểu cấu trúc và vai trò của các phần tử trên trang.

Ví dụ Thực hành 1: Nút bấm (Button)

  • Cách kém accessible: Sử dụng div giả làm nút.

    <div onclick="handleSave()" style="cursor: pointer; padding: 10px; background: blue; color: white;">
      Lưu dữ liệu
    </div>
    
    • Giải thích: Mặc dù bạn có thể thêm onclick và style để nó trông giống nút, nhưng thẻ div không có ngữ nghĩa của một nút. Người dùng bàn phím sẽ không thể nhấn tab để focus vào nó (trừ khi thêm tabindex), và screen reader sẽ chỉ đọc nó như một div thông thường, không truyền tải được rằng đây là một phần tử tương tác.
  • Cách accessible: Sử dụng thẻ <button>.

    <button onclick="handleSave()">
      Lưu dữ liệu
    </button>
    
    • Giải thích: Thẻ <button> tự động có ngữ nghĩa của một nút. Nó có thể nhận focus khi nhấn tab, có thể được kích hoạt bằng phím Space hoặc Enter. Screen reader sẽ nhận diện nó là một nút và thông báo cho người dùng. Đơn giản hơn, mạnh mẽ hơn, và accessible hơn.

Ví dụ Thực hành 2: Thanh điều hướng (Navigation)

  • Cách kém accessible: Chỉ dùng divul/li.

    <div>
      <ul>
        <li><a href="/">Trang chủ</a></li>
        <li><a href="/products">Sản phẩm</a></li>
        <li><a href="/contact">Liên hệ</a></li>
      </ul>
    </div>
    
    • Giải thích: Mặc dù ul/li cho biết đây là một danh sách, thẻ div bao ngoài không nói lên vai trò điều hướng chính của khối này.
  • Cách accessible: Sử dụng thẻ <nav>.

    <nav>
      <ul>
        <li><a href="/">Trang chủ</a></li>
        <li><a href="/products">Sản phẩm</a></li>
        <li><a href="/contact">Liên hệ</a></li>
      </ul>
    </nav>
    
    • Giải thích: Thẻ <nav> báo hiệu cho screen reader và các công nghệ hỗ trợ khác rằng đây là một khối chứa các liên kết điều hướng chính của trang. Người dùng screen reader thường có phím tắt để "nhảy" thẳng đến các khối <nav>, giúp họ di chuyển nhanh chóng trên trang.

2. Hình Ảnh Không Chỉ Để Nhìn: Thuộc Tính alt

Hình ảnh là một phần quan trọng của web, nhưng không phải ai cũng có thể nhìn thấy chúng (người dùng khiếm thị, kết nối mạng chậm, hình ảnh lỗi). Thuộc tính alt (alternative text) là cách chúng ta cung cấp thông tin thay thế cho hình ảnh.

  • Tại sao? alt text được screen reader đọc lên, hiển thị khi hình ảnh không load được, và cả các công cụ tìm kiếm cũng sử dụng nó.

Ví dụ Thực hành:

  • Cách kém accessible: Thiếu alt hoặc alt không mô tả.

    <img src="banner.jpg"> <!-- Thiếu alt -->
    <img src="product123.png" alt="image"> <!-- alt không mô tả -->
    
    • Giải thích: Screen reader sẽ bỏ qua hình ảnh thiếu alt (trừ khi nó là liên kết hoặc có vai trò phức tạp), hoặc đọc lên "image", điều này không giúp ích gì cho người dùng.
  • Cách accessible: Viết alt mô tả ngắn gọný nghĩa của hình ảnh trong ngữ cảnh của trang.

    <img src="banner-sale.jpg" alt="Banner quảng cáo giảm giá 50% toàn bộ sản phẩm nhân dịp lễ 30/4">
    <img src="cat-sleeping.jpg" alt="Một chú mèo Tabby màu cam đang cuộn tròn ngủ say trên ghế sofa màu xanh dương">
    
    • Giải thích: alt text này cung cấp thông tin hữu ích, giúp người dùng không nhìn thấy hình ảnh vẫn hiểu được nội dung hoặc thông điệp mà hình ảnh muốn truyền tải.
    • Lưu ý: Nếu hình ảnh chỉ mang tính trang trí và không truyền tải thông tin quan trọng (ví dụ: icon trang trí không kèm text, đường viền hoa văn), hãy sử dụng alt="" để screen reader bỏ qua nó.
    <img src="decorative-line.svg" alt=""> <!-- Screen reader sẽ bỏ qua -->
    

3. Biểu Mẫu Thân Thiện Với Người Dùng (Accessible Forms)

Biểu mẫu là nơi người dùng tương tác nhiều nhất. Đảm bảo các input trong form có thể được hiểu và sử dụng dễ dàng là cực kỳ quan trọng.

  • Tại sao? Liên kết label với input giúp screen reader đọc nhãn khi focus vào input, và cho phép người dùng click vào nhãn để focus vào ô nhập liệu tương ứng.

Ví dụ Thực hành:

  • Cách kém accessible: Chỉ dùng placeholder hoặc text thông thường làm nhãn.

    <input type="text" placeholder="Nhập email của bạn">
    <p>Mật khẩu:</p><input type="password">
    
    • Giải thích: placeholder sẽ biến mất khi người dùng bắt đầu nhập và không được screen reader đọc một cách đáng tin cậy như một nhãn cố định. Text thông thường không có liên kết ngữ nghĩa với input.
  • Cách accessible: Sử dụng thẻ <label> với thuộc tính for trỏ đến id của input.

    <label for="email">Địa chỉ Email:</label>
    <input type="email" id="email" required>
    
    <label for="password">Mật khẩu:</label>
    <input type="password" id="password" required aria-describedby="password-help">
    <small id="password-help">Mật khẩu phải có ít nhất 8 ký tự, bao gồm chữ hoa, chữ thường và số.</small>
    
    • Giải thích:
      • Thuộc tính for="email" trên <label> liên kết nó với <input id="email">. Screen reader sẽ đọc "Địa chỉ Email" khi focus vào ô nhập email. Click vào text "Địa chỉ Email" cũng sẽ focus vào ô nhập.
      • Thuộc tính required cung cấp ngữ nghĩa báo hiệu đây là trường bắt buộc.
      • aria-describedby="password-help" liên kết input mật khẩu với phần tử có id="password-help". Screen reader sẽ đọc thông tin hướng dẫn thêm về mật khẩu sau khi đọc nhãn và loại input, cung cấp ngữ cảnh đầy đủ hơn.

4. Tương Tác Bằng Bàn Phím (Keyboard Navigation)

Không phải ai cũng sử dụng chuột. Nhiều người dùng (ví dụ: người dùng gặp khó khăn về vận động, người dùng screen reader) điều hướng website hoàn toàn bằng bàn phím (thường là phím Tab).

  • Tại sao? Đảm bảo tất cả các phần tử tương tác (liên kết, nút, form controls) đều có thể focus bằng bàn phím theo một thứ tự hợp lý là điều kiện tiên quyết.

Ví dụ Thực hành:

  • Kiểm tra: Mở website của bạn và thử nhấn phím Tab liên tục. Các phần tử tương tác có nhận focus không? Thứ tự focus có đúng logic không? (Ví dụ: từ trên xuống dưới, từ trái sang phải theo luồng đọc).
  • Sửa lỗi:

    • Sử dụng HTML ngữ nghĩa: Như đã nói ở mục 1, sử dụng <button>, <a>, <input>, <select>, <textarea>... đảm bảo khả năng focus mặc định. Tránh sử dụng div hoặc span cho các yếu tố tương tác mà không thêm tabindex="0" và xử lý sự kiện keypress (Enter/Space) bằng JavaScript. Nhưng tốt nhất vẫn là dùng thẻ gốc.
    • Tránh thay đổi thứ tự Tab vô tội vạ: Thuộc tính tabindex có thể nhận giá trị dương (>0), nhưng việc sử dụng nó để ép buộc thứ tự focus rất dễ gây nhầm lẫn và thường được coi là anti-pattern trừ khi thực sự cần thiết cho các widget phức tạp. Hãy để luồng HTML tự nhiên quyết định thứ tự Tab.
    • Đảm bảo trạng thái focus hiển thị rõ ràng: Trình duyệt cung cấp outline mặc định khi focus, nhưng đôi khi CSS của bạn ghi đè hoặc ẩn nó (outline: none). Tuyệt đối không ẩn outline trừ khi bạn cung cấp một chỉ báo focus thậm chí còn rõ ràng hơn.
    /* Đảm bảo outline hiển thị */
    *:focus {
      outline: 2px solid blue !important; /* Sử dụng màu nổi bật và !important nếu cần */
      outline-offset: 2px; /* Tạo khoảng cách giữa outline và phần tử */
    }
    
    /* Hoặc tạo hiệu ứng focus tùy chỉnh nhưng phải rõ ràng */
    button:focus {
      box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.5);
      outline: none; /* Chỉ khi đã có box-shadow thay thế */
    }
    
    • Giải thích: Viền outline (hoặc hiệu ứng tương tự) hiển thị khi một phần tử được focus là quan trọng sống còn đối với người dùng bàn phím để biết họ đang ở đâu trên trang.

5. Màu Sắc và Độ Tương Phản (Color & Contrast)

Màu sắc truyền tải thông tin, nhưng không phải ai cũng nhìn nhận màu sắc như nhau (người mù màu) hoặc có thể phân biệt rõ ràng các màu có độ tương phản thấp (người cao tuổi, người khiếm thị).

  • Tại sao? Độ tương phản màu đủ lớn giữa text và nền đảm bảo nội dung dễ đọc đối với nhiều đối tượng người dùng. Không dựa hoàn toàn vào màu sắc để truyền tải thông tin quan trọng.

Ví dụ Thực hành:

  • Cách kém accessible: Sử dụng màu chữ và màu nền quá gần nhau, hoặc dùng màu đỏ/xanh lá cây để báo lỗi/thành công mà không có chỉ báo nào khác.

    /* Khó đọc */
    .low-contrast {
      color: #aaaaaa; /* Màu xám nhạt */
      background-color: #f0f0f0; /* Nền xám rất nhạt */
    }
    
    /* Dựa hoàn toàn vào màu sắc */
    .status-indicator {
      color: red; /* Lỗi */
    }
    .status-indicator.success {
      color: green; /* Thành công */
    }
    
    • Giải thích: Sự kết hợp màu này có thể rất khó đọc. Việc chỉ dùng màu để báo trạng thái sẽ khiến người mù màu gặp khó khăn.
  • Cách accessible: Đảm bảo độ tương phản màu đạt chuẩn WCAG (Web Content Accessibility Guidelines). Cung cấp chỉ báo bổ sung ngoài màu sắc.

    /* Tốt hơn, độ tương phản cao hơn */
    .high-contrast {
      color: #333333; /* Màu xám đậm */
      background-color: #ffffff; /* Nền trắng */
    }
    
    /* Sử dụng icon hoặc text bổ sung */
    .status-indicator.error::before {
      content: "!"; /* Thêm icon hoặc text */
      margin-right: 5px;
    }
     .status-indicator.success::before {
      content: "✓"; /* Thêm icon hoặc text */
      margin-right: 5px;
    }
    .status-indicator.error {
      color: red; /* Vẫn dùng màu */
      font-weight: bold; /* Thêm độ đậm */
    }
    .status-indicator.success {
      color: green; /* Vẫn dùng màu */
      font-weight: bold; /* Thêm độ đậm */
    }
    
    • Giải thích: Sử dụng các công cụ kiểm tra độ tương phản (có rất nhiều online, chỉ cần tìm kiếm "color contrast checker"). Đối với chỉ báo trạng thái, kết hợp màu sắc với icon, text, hoặc làm đậm chữ giúp tất cả người dùng đều nhận được thông tin, bất kể khả năng phân biệt màu sắc.

6. ARIA: Khi HTML Chưa Đủ (When HTML Isn't Enough)

Đôi khi, HTML ngữ nghĩa mặc định không đủ để mô tả các widget phức tạp hoặc nội dung cập nhật động (ví dụ: tab panel, modal dialog, live region thông báo). Đó là lúc ARIA (Accessible Rich Internet Applications) phát huy tác dụng.

  • Tại sao? ARIA là tập hợp các thuộc tính bạn thêm vào HTML để cung cấp thông tin ngữ nghĩa và vai trò bổ sung cho screen reader và các công nghệ hỗ trợ khác.

Lưu ý Quan trọng nhất về ARIA: Quy tắc số 1 của ARIA: Không sử dụng ARIA nếu một phần tử HTML gốc đã có ngữ nghĩa và chức năng tương tự. Hãy luôn ưu tiên HTML thuần túy.

Ví dụ Thực hành (Sử dụng ARIA một cách có chủ đích):

  • Kịch bản: Một nút bấm tùy chỉnh (ví dụ: dùng div hoặc icon) mà bạn muốn screen reader nhận diện là nút và có thể focus/kích hoạt. (Nhắc lại: luôn ưu tiên <button>)

    <!-- Chỉ dùng khi không thể dùng <button> vì lý do nào đó của thư viện UI, v.v. -->
    <div role="button" tabindex="0" aria-label="Đóng cửa sổ pop-up" onclick="closePopup()">
      <img src="/icons/close.svg" alt="">
    </div>
    
    • Giải thích:
      • role="button": Nói cho screen reader biết phần tử này đóng vai trò là một nút.
      • tabindex="0": Cho phép phần tử nhận focus khi nhấn Tab.
      • aria-label="Đóng cửa sổ pop-up": Cung cấp nhãn văn bản cho screen reader, đặc biệt hữu ích khi nội dung hiển thị chỉ là icon. Screen reader sẽ đọc nhãn này thay vì cố gắng mô tả cái icon.
  • Kịch bản: Một khu vực nội dung trên trang được cập nhật mà không cần tải lại trang (ví dụ: thông báo lỗi gửi form thành công).

    <div id="status-message" aria-live="polite">
      <!-- Nội dung thông báo sẽ được JavaScript thêm vào đây -->
    </div>
    
    // Ví dụ JavaScript (không đầy đủ)
    function displaySuccessMessage() {
      const statusDiv = document.getElementById('status-message');
      statusDiv.textContent = "Dữ liệu đã được lưu thành công!";
      // Screen reader sẽ tự động đọc nội dung mới trong div này do aria-live="polite"
    }
    
    • Giải thích: aria-live="polite" biến phần tử này thành một "live region". Khi nội dung bên trong div thay đổi bằng JavaScript, screen reader sẽ thông báo cho người dùng về sự thay đổi đó một cách "lịch sự" (chờ người dùng hoàn thành tác vụ hiện tại trước khi thông báo).

Comments

There are no comments at the moment.