UX Architect

Accessibility (a11y): Thiết kế cho mọi đối tượng (theo tiêu chuẩn WCAG)

7/19/2026 · 12p đọc


title: "Accessibility (a11y): Thiết kế cho mọi đối tượng (theo tiêu chuẩn WCAG)"
series: "UX Architect: 40 bài Expert-Deep Dive"
part: "Phần 2 — Kiến trúc Thông tin & Tư duy Logic"
order: 20
audience: "Product Designer & UX Architect"
reading_time: "13 phút"
tags: ["accessibility", "a11y", "WCAG", "ARIA", "inclusive-design", "semantic-html", "keyboard-navigation"]

Accessibility (a11y): Thiết kế cho mọi đối tượng (theo tiêu chuẩn WCAG)

Một buổi demo trước khách hàng doanh nghiệp (enterprise), team bán hàng hào hứng trình bày dashboard mới — biểu đồ đẹp, màu sắc tinh tế, animation mượt. Đến giữa buổi, người phụ trách mua sắm (procurement) phía khách hàng hỏi một câu khiến cả phòng họp im lặng: "Sản phẩm của các bạn có đáp ứng VPAT (Voluntary Product Accessibility Template) không? Chúng tôi bắt buộc phải có tài liệu này cho mọi phần mềm mua vào." Không ai trong team trả lời được, vì chưa từng ai hỏi câu đó khi thiết kế.

Đây không phải tình huống hiếm. Accessibility (khả năng tiếp cận — thiết kế để người khuyết tật có thể sử dụng sản phẩm bình đẳng) trong phần lớn tổ chức bị xếp vào nhóm "làm sau, nếu có thời gian" — một dòng trong backlog mãi không được ưu tiên cho đến khi có yêu cầu pháp lý, một khách hàng lớn đòi hỏi, hoặc tệ hơn là một vụ kiện. Hệ quả không chỉ là rủi ro pháp lý: một phần đáng kể người dùng — khiếm thị dùng screen reader, người khó vận động chỉ thao tác bằng bàn phím, người khiếm thính cần phụ đề, người có suy giảm nhận thức cần giao diện rõ ràng — bị loại khỏi sản phẩm ngay từ thiết kế, chứ không phải vì họ "không phù hợp" với sản phẩm.

Bài này khép lại Phần 2 của series — Kiến trúc Thông tin & Tư duy Logic — bằng chính lý do IA (kiến trúc thông tin) và luồng người dùng chỉ thực sự hoàn chỉnh khi nó phục vụ được tất cả mọi người, không chỉ nhóm người dùng "trung bình" trong persona.

The UX Problem

Vấn đề cốt lõi không phải là thiếu công cụ kiểm tra tương phản màu hay thiếu ý thức về "màu chữ trên nền". Vấn đề là accessibility bị hiểu sai bản chất — nó bị coi là một lớp trang trí bổ sung, một checklist tuân thủ (compliance), thay vì một thuộc tính cấu trúc của sản phẩm, giống như hiệu năng (performance) hay bảo mật (security).

Ba nguyên nhân khiến pattern này lặp lại ở hầu hết tổ chức:

  1. Vô hình với phần lớn team. Designer và PM không tự mình trải nghiệm rào cản — họ nhìn thấy, click chuột, phân biệt màu bình thường. Vấn đề chỉ hiện ra khi có người dùng thật gặp khó khăn, và lúc đó sản phẩm đã ra mắt.
  2. Không nằm trong định nghĩa "xong" (Definition of Done). Khi accessibility không phải tiêu chí nghiệm thu, nó tự động rơi khỏi phạm vi ưu tiên khi deadline gấp — một quy luật gần như chắc chắn trong mọi dự án phần mềm.
  3. Bị xử lý ở sai giai đoạn. Nhiều team cố "vá" accessibility ở bước code review hoặc QA cuối cùng — quá muộn để sửa cấu trúc, chỉ có thể sửa bề mặt (thêm alt text, chỉnh màu), trong khi vấn đề gốc — trật tự focus sai, thiếu ngữ nghĩa (semantic) trong component — đã nằm sâu trong kiến trúc và tốn kém gấp nhiều lần để sửa lại.

Hệ quả kinh doanh (business) không hề nhỏ: WHO ước tính hơn 1 tỷ người trên thế giới sống với một dạng khuyết tật nào đó (số liệu công khai, mang tính minh hoạ quy mô, không phải số liệu nội bộ sản phẩm) — bỏ qua accessibility đồng nghĩa với việc chủ động loại bỏ một phân khúc thị trường, đồng thời tự đóng cửa với các hợp đồng B2B/chính phủ vốn thường bắt buộc chứng nhận tuân thủ.

Design Framework

Chuẩn quốc tế được công nhận rộng rãi nhất cho accessibility trên web là WCAG (Web Content Accessibility Guidelines), do W3C xây dựng. WCAG tổ chức toàn bộ yêu cầu quanh bốn nguyên tắc nền tảng, viết tắt là POUR:

1. Perceivable (Có thể cảm nhận) — thông tin và giao diện phải được trình bày theo cách mà người dùng có thể nhận biết được, bất kể giác quan nào bị hạn chế.

  • Văn bản thay thế (alt text) cho hình ảnh mang thông tin.
  • Tỷ lệ tương phản màu đủ giữa chữ và nền.
  • Phụ đề (caption) cho video, transcript cho audio.
  • Không truyền tải thông tin CHỈ bằng màu sắc (ví dụ: chỉ dùng màu đỏ/xanh để báo lỗi/thành công mà không kèm icon hoặc text).

2. Operable (Có thể thao tác) — mọi chức năng phải dùng được không chỉ bằng chuột.

  • Toàn bộ luồng thao tác được bằng bàn phím (keyboard-only navigation).
  • Không có bẫy focus (focus trap) khiến người dùng kẹt lại ở một thành phần.
  • Đủ thời gian cho các tác vụ có giới hạn thời gian (hoặc cho phép gia hạn).
  • Không dùng nội dung nhấp nháy gây co giật (seizure risk).

3. Understandable (Có thể hiểu) — nội dung và cách vận hành phải dễ đoán.

  • Ngôn ngữ rõ ràng, nhất quán về thuật ngữ.
  • Hành vi giao diện dự đoán được (một thành phần giống nhau phải hành xử giống nhau ở mọi nơi).
  • Hướng dẫn lỗi cụ thể, chỉ đúng chỗ sai và cách sửa.

4. Robust (Vững chắc) — nội dung phải hoạt động tốt với nhiều công nghệ hỗ trợ (assistive technology) khác nhau, kể cả những công nghệ ra đời sau này.

  • HTML hợp lệ, ngữ nghĩa đúng.
  • Tương thích với screen reader (NVDA, JAWS, VoiceOver...) hiện tại và tương lai.

WCAG chia mức độ tuân thủ thành ba cấp — A (tối thiểu, các rào cản nghiêm trọng nhất), AA (chuẩn thực tế phần lớn tổ chức và luật lệ khu vực yêu cầu — ví dụ ADA tại Mỹ, EN 301 549 tại EU thường tham chiếu tới AA), và AAA (mức cao nhất, thường chỉ áp dụng cho một số nội dung đặc thù vì có những tiêu chí AAA không khả thi cho mọi loại nội dung). Với sản phẩm SaaS B2B, mục tiêu thực tế nên là AA — đây là điểm cân bằng giữa tính khả thi kỹ thuật và mức độ bao phủ người dùng.

Điều quan trọng: POUR không phải bốn hạng mục độc lập để tick riêng lẻ — chúng đan xen. Một modal thiếu quản lý focus vừa vi phạm Operable (không thao tác được bằng bàn phím) vừa vi phạm Robust (screen reader không hiểu được ngữ cảnh).

The "Architect's" Insight

Đây là điểm khác biệt cốt lõi mà một UX Architect phải nắm được: accessibility không phải là việc "vẽ cho đẹp và rõ ràng" — nó là một hợp đồng kỹ thuật (technical contract) giữa thiết kế và code, và hợp đồng này được thực thi thông qua hai thứ cụ thể: HTML ngữ nghĩa (semantic HTML)thuộc tính ARIA (Accessible Rich Internet Applications).

Hãy nhìn vào sự khác biệt này ở cấp độ code, vì đây là nơi quyết định UX biến thành nợ kỹ thuật (technical debt) hay tài sản kỹ thuật:

<!-- Cách 1: "Vẽ giống nút" nhưng không phải nút -->
<div class="button-style" onclick="submitForm()">Lưu</div>

<!-- Cách 2: Đúng ngữ nghĩa, screen reader hiểu ngay đây là hành động -->
<button type="submit" aria-label="Lưu thông tin khách hàng">Lưu</button>

Với mắt thường, cả hai <div><button> có thể được style giống hệt nhau (padding, màu, border-radius). Nhưng với một screen reader hoặc điều hướng bàn phím:

  • <div> không nhận được focus khi nhấn Tab, không có role="button" mặc định, không phản hồi phím Enter/Space, và screen reader sẽ đọc nó như một đoạn văn bản vô nghĩa thay vì một hành động khả thi.
  • <button> tự động có toàn bộ hành vi đó — miễn phí, không cần code thêm.

Đây chính là nơi quyết định thiết kế lan trực tiếp vào kiến trúc frontend: nếu designer chỉ định "component nào là gì" (một dropdown thực sự là <select> hay role="listbox", một modal là role="dialog" với aria-modal="true", một thông báo lỗi là role="alert") ngay từ file thiết kế, kỹ sư frontend implement đúng ngay từ đầu bằng các phần tử HTML gốc hoặc thư viện component đã accessible-ready. Ngược lại, nếu thiết kế chỉ đưa ra hình ảnh tĩnh mà không đặc tả hành vi và vai trò ngữ nghĩa, kỹ sư buộc phải "đoán" — và trong 90% trường hợp áp lực deadline, họ sẽ chọn giải pháp nhanh nhất: dùng <div> + CSS + onClick, gắn thêm vài thuộc tính ARIA a chắp vá về sau (nếu còn nhớ), dẫn tới ARIA sai ngữ cảnh — thứ còn tệ hơn không có ARIA gì cả, vì nó đánh lừa công nghệ hỗ trợ rằng phần tử này hoạt động đúng trong khi thực tế không.

Hệ quả kiến trúc còn đi xa hơn ở tầng component system: một design system (hệ thống thiết kế — sẽ bàn kỹ ở Bài 21) nếu không đóng gói sẵn accessibility ở cấp component gốc (base component), thì MỌI feature dùng lại component đó đều kế thừa lỗi. Ví dụ, nếu component <Modal> trong thư viện component dùng chung của tổ chức không tự quản lý focus trap và aria-modal, thì hàng chục feature khác nhau — từ "Xác nhận xoá khách hàng" đến "Tạo deal mới" — đều sẽ bị lỗi tương tự, và khi phát hiện ra (thường là qua một audit hoặc khiếu nại), team phải sửa ở HÀNG CHỤC nơi thay vì MỘT nơi duy nhất. Đây là lý do accessibility nên được giải quyết ở tầng design system/component library, không phải ở tầng feature — một quyết định kiến trúc, không phải quyết định thẩm mỹ.

Cuối cùng, có một hệ quả ít ai nghĩ tới: accessibility ảnh hưởng cả tầng dữ liệu và nội dung. Ví dụ, nếu một trường trong database chỉ lưu status_color: "red" mà không lưu status_label: "Quá hạn", frontend không có gì để đưa vào aria-label hoặc text hiển thị thay thế cho màu — nghĩa là quyết định schema (lược đồ dữ liệu) ở tầng backend cũng cần cân nhắc accessibility, không chỉ là "đủ dữ liệu để vẽ UI cho người nhìn thấy".

Practical Example

Tình huống: thiết kế modal "Xoá vĩnh viễn khách hàng" trong một CRM — một hành động nguy hiểm, cần xác nhận.

Đặc tả UX cần có (không chỉ là hình ảnh):

Hạng mục Yêu cầu cụ thể Vai trò kỹ thuật tương ứng
Vai trò semantic Modal phải có role="dialog", aria-modal="true", aria-labelledby trỏ tới tiêu đề Screen reader biết đây là một dialog độc lập, không phải nội dung trang nền
Focus khi mở Focus tự động chuyển vào phần tử đầu tiên trong modal (thường là nút "Huỷ" — an toàn hơn nút "Xoá") Người dùng bàn phím/screen reader không bị lạc ở nền trang
Focus trap Tab/Shift+Tab chỉ xoay vòng trong các phần tử của modal, không thoát ra ngoài Ngăn người dùng vô tình tương tác với trang nền trong khi modal đang mở
Phím thoát Esc đóng modal, focus trả về đúng phần tử đã trigger mở modal Người dùng không cần chuột để thoát, không bị "mất vị trí" sau khi đóng
Thông báo động Khi xoá thành công, dùng role="alert" hoặc aria-live="polite" cho toast xác nhận Screen reader tự động đọc kết quả mà không cần người dùng dò tìm

Pseudo-code minh hoạ phần focus trap — chi tiết mà nếu không được đặc tả trong thiết kế, kỹ sư thường bỏ sót vì nó không "nhìn thấy được" trên mockup tĩnh:

function ConfirmDeleteModal({ isOpen, onClose, onConfirm }) {
  const cancelButtonRef = useRef(null);
  const triggerElementRef = useRef(document.activeElement);

  useEffect(() => {
    if (isOpen) {
      cancelButtonRef.current?.focus(); // focus vào nút an toàn khi mở
    }
    return () => {
      triggerElementRef.current?.focus(); // trả focus khi đóng
    };
  }, [isOpen]);

  return (
    <div role="dialog" aria-modal="true" aria-labelledby="modal-title"
         onKeyDown={(e) => e.key === 'Escape' && onClose()}>
      <FocusTrap>
        <h2 id="modal-title">Xoá vĩnh viễn khách hàng?</h2>
        <p>Hành động này không thể hoàn tác.</p>
        <button ref={cancelButtonRef} onClick={onClose}>Huỷ</button>
        <button onClick={onConfirm}>Xoá</button>
      </FocusTrap>
    </div>
  );
}

Nếu file thiết kế chỉ đưa hình ảnh modal mà không kèm bảng đặc tả hành vi ở trên, xác suất rất cao là kỹ sư sẽ build một modal "trông giống hệt" nhưng thiếu toàn bộ focus trap và ARIA — sản phẩm vẫn "đẹp" trên demo nhưng người dùng screen reader gặp một modal mà họ không thể thoát ra, phải reload cả trang.

Common Pitfalls

  1. Chỉ kiểm tra độ tương phản màu rồi coi như đã "làm accessibility". Đây là lỗi phổ biến nhất vì công cụ kiểm tra contrast dễ dùng, cho kết quả tức thì, tạo cảm giác "đã tuân thủ". Nhưng contrast chỉ là một tiêu chí trong hàng chục tiêu chí của Perceivable — trong khi phần lớn rào cản thực tế nằm ở Operable (điều hướng bàn phím) và Robust (cấu trúc ngữ nghĩa), những thứ không thể kiểm tra bằng cách nhìn màu sắc.

  2. Coi accessibility là một "dự án" làm sau, tách biệt khỏi luồng thiết kế chính. Khi accessibility được xử lý như một sprint riêng "để dọn nợ" sau khi feature đã ra mắt, chi phí sửa cao gấp nhiều lần so với đặc tả đúng từ bước wireframe — vì lúc đó phải sửa cả cấu trúc component đã được nhiều feature khác tái sử dụng, thay vì chỉ đặc tả một lần.

  3. Đặc tả trạng thái tương tác không đầy đủ trong design file. Designer thường chỉ vẽ trạng thái "default" và "hover", quên trạng thái :focus-visible (viền/chỉ báo khi điều hướng bằng bàn phím). Không có đặc tả này, kỹ sư dễ xoá luôn outline focus mặc định của trình duyệt vì "nhìn xấu" — hành động này vô hiệu hoá hoàn toàn khả năng điều hướng bằng bàn phím trên toàn bộ giao diện.

Checklist

  • Mọi hình ảnh mang thông tin có alt text mô tả đúng nội dung (hình trang trí thuần tuý dùng alt="").
  • Tỷ lệ tương phản chữ/nền đạt tối thiểu mức AA (4.5:1 với chữ thường, 3:1 với chữ lớn).
  • Toàn bộ luồng thao tác chính (tạo, sửa, xoá, xác nhận) thực hiện được chỉ bằng bàn phím, không cần chuột.
  • Modal, dropdown, menu có quản lý focus rõ ràng (focus vào khi mở, focus trap khi đang mở, trả focus khi đóng).
  • Thông tin trạng thái (lỗi, thành công, cảnh báo) không chỉ dựa vào màu sắc — luôn kèm icon hoặc text.
  • Component tương tác dùng đúng phần tử HTML ngữ nghĩa (button, a, select...) trước khi cân nhắc ARIA bổ sung.
  • Đặc tả thiết kế có ghi rõ role/aria-* cần thiết cho các component phức tạp (modal, tab, accordion, combobox) để kỹ sư không phải tự đoán.

🔗 Bài viết liên quan: Designing for Complexity, Cognitive Load & Progressive Disclosure


Bài trước: Designing for Complexity · Bài tiếp theo: Design System

Accessibility (a11y): Thiết kế cho mọi đối tượng (theo tiêu chuẩn WCAG)