nicetool.dev logo
#accessibility#css#design

Độ tương phản màu WCAG: Vì sao là 4.5:1 và làm sao để đạt chuẩn

Tỷ lệ tương phản WCAG đo cái gì, ngưỡng AA và AAA chính xác, các lỗi hay gặp (làm tròn, màu trong suốt, dark mode, chữ trên ảnh) và cách sửa nhanh màu không đạt.

7 phút đọc

Chữ xám nhạt trên nền trắng trông rất "sạch" trong bản thiết kế. Nhưng mở trên điện thoại ngoài nắng, trên màn hình văn phòng giá rẻ, hoặc với người ngoài bốn mươi tuổi, đoạn chữ đó gần như không đọc được. Tỷ lệ tương phản WCAG biến câu hỏi cảm tính "đọc có rõ không?" thành một con số có thể kiểm tra khi review code. Bài viết này giải thích con số đó đến từ đâu, ngưỡng nào áp dụng cho trường hợp nào, và những lỗi khiến giao diện không đạt dù designer đã "kiểm tra rồi".

Tỷ lệ tương phản đo cái gì?

WCAG 2.x so sánh độ chói tương đối (relative luminance) của hai màu, tức là mắt người cảm nhận màu đó sáng đến mức nào, trên thang từ 0 (đen) đến 1 (trắng). Đây không phải chữ "L" trong HSL. Vàng thuần và xanh dương thuần đều có độ sáng 50% trong HSL, nhưng màu vàng chói gấp khoảng 13 lần.

Độ chói được tính qua ba bước:

  1. Đổi từng kênh sRGB từ 0-255 về 0-1.
  2. "Tuyến tính hóa" để bỏ đường cong gamma: giá trị không quá 0.04045 thì chia cho 12.92, giá trị lớn hơn dùng ((c + 0.055) / 1.055) ^ 2.4.
  3. Nhân trọng số theo độ nhạy của mắt: L = 0.2126 R + 0.7152 G + 0.0722 B.

Kênh xanh lá chiếm phần lớn trọng số, xanh dương chiếm rất ít. Vì vậy chữ xanh dương đậm trên nền trắng rất dễ đọc, còn chữ xanh lá nhạt thì hầu như không bao giờ đạt.

Tỷ lệ giữa màu sáng hơn L1 và màu tối hơn L2:

ratio = (L1 + 0.05) / (L2 + 0.05)

Số 0.05 đại diện cho ánh sáng môi trường phản chiếu trên màn hình. Kết quả nằm trong khoảng 1:1 (hai màu giống nhau) đến 21:1 (đen trên trắng).

Các ngưỡng cần nhớ

Yêu cầu Mức Tỷ lệ tối thiểu
Chữ thường (1.4.3) AA 4.5:1
Chữ lớn (1.4.3) AA 3:1
Chữ thường (1.4.6) AAA 7:1
Chữ lớn (1.4.6) AAA 4.5:1
Thành phần giao diện và đồ họa (1.4.11) AA 3:1

"Chữ lớn" nghĩa là từ 24px với độ đậm thường, hoặc từ 18.66px nếu in đậm (bản gốc ghi 18pt và 14pt). Một tiêu đề 20px semibold không được tính là chữ lớn, nên vẫn cần 4.5:1.

Gần như ai cũng nhắm tới mức AA. Đây là mức được các luật và tiêu chuẩn tham chiếu, chẳng hạn tiêu chuẩn châu Âu EN 301 549 (dùng cho Đạo luật Hỗ trợ Tiếp cận châu Âu - EAA) và quy định của nhiều cơ quan nhà nước. AAA là mục tiêu tốt cho phần nội dung chính, nhưng thường không bắt buộc cho toàn bộ website.

Tiêu chí 1.4.11 (thêm vào từ WCAG 2.1) hay bị bỏ quên. Viền ô nhập liệu, viền checkbox, vòng focus, icon mang ý nghĩa và đường trong biểu đồ đều cần 3:1 so với màu ngay cạnh nó. Một ô input viền #e5e7eb trên nền trắng (1.24:1) gần như vô hình với nhiều người.

Một số thứ được miễn: nút đang bị vô hiệu hóa (disabled), chi tiết thuần trang trí, chữ nằm trong ảnh chụp và logo.

Số liệu thực tế từ các bảng màu phổ biến

Quy tắc trừu tượng sẽ dễ nhớ hơn khi có con số cụ thể. Các tỷ lệ dưới đây được tính bằng công thức ở trên:

Màu chữ Màu nền Tỷ lệ AA chữ thường
#777777 xám trắng 4.47:1 Không đạt
#767676 xám trắng 4.54:1 Đạt
#999999 xám trắng 2.85:1 Không đạt
trắng #007bff (primary Bootstrap 4) 3.98:1 Không đạt
trắng #0d6efd (primary Bootstrap 5) 4.50:1 Vừa đủ đạt
trắng #3b82f6 (blue-500 Tailwind v3) 3.68:1 Không đạt
trắng #2563eb (blue-600 Tailwind v3) 5.17:1 Đạt
trắng #22c55e (green-500 Tailwind v3) 2.28:1 Không đạt
trắng #15803d (green-700 Tailwind v3) 5.02:1 Đạt
#9ca3af (gray-400 Tailwind v3) trắng 2.54:1 Không đạt

Có hai bài học rõ ràng. Thứ nhất, #767676 là màu xám trung tính nhạt nhất còn đạt chuẩn trên nền trắng tinh, mọi màu xám nhạt hơn đều không dùng được cho chữ nội dung. Thứ hai, sắc độ "500" kinh điển của hầu hết bảng màu đều quá sáng để làm nền cho chữ trắng trên nút bấm. Bootstrap 5 dùng màu xanh primary đậm hơn Bootstrap 4 một chút, và chỉ chừng đó đã đủ để vượt ngưỡng 4.5:1.

Nút màu xanh lá là cái bẫy đặc biệt. Vì kênh xanh lá chiếm phần lớn độ chói, một màu xanh lá trông đậm và rực vẫn thường quá sáng cho chữ trắng. Bạn thường phải chọn đậm hơn hai bậc so với cảm giác ban đầu.

Năm lỗi hay gặp

1. Làm tròn lên

Ngưỡng WCAG rất chặt. Tỷ lệ 4.47:1 là không đạt, dù vài công cụ hiển thị thành "4.5". Công cụ Kiểm tra độ tương phản màu của nicetool.dev cắt bớt chữ số thay vì làm tròn, nên khi báo đạt là đạt thật.

2. Bỏ qua độ trong suốt

Designer rất thích dùng rgba(0, 0, 0, 0.5) cho chữ phụ. Độ tương phản của nó phụ thuộc hoàn toàn vào thứ nằm bên dưới. Trên nền trắng, đen 50% trở thành #808080, chỉ đạt 3.95:1 và không qua chuẩn. Luôn trộn màu trong suốt với nền thật trước khi đo. Điều này cũng đúng khi đặt opacity cho phần tử cha.

3. Chữ placeholder và gợi ý

Placeholder vẫn là chữ. Nhiều bộ CSS reset và UI kit dùng placeholder xám nhạt khoảng 2.5:1. Nếu placeholder chứa thông tin, ví dụ định dạng "DD/MM/YYYY", nó cần 4.5:1. Tốt hơn nữa là đưa thông tin đó vào nhãn hoặc dòng gợi ý hiển thị bên dưới ô nhập.

4. Quên dark mode

Mỗi giao diện sáng/tối là một bản thiết kế riêng và cần kiểm tra riêng. slate-500 (#64748b) của Tailwind v3 trên nền trắng đạt 4.76:1, nhưng cùng màu đó trên nền slate-900 (#0f172a) chỉ còn 3.75:1. Ở dark mode bạn thường phải chuyển sang sáng hơn một hai bậc, ví dụ slate-400 (6.96:1).

5. Chữ trên ảnh và gradient

Tiêu đề trên ảnh banner có thể đạt ở phần ảnh tối nhưng không đạt ở một mảng trời sáng. Hãy đo độ tương phản với vùng sáng nhất phía sau chữ (hoặc vùng tối nhất nếu chữ màu tối). Cách sửa chắc chắn là thêm lớp phủ bán trong suốt hoặc text-shadow, kèm một màu nền đặc làm dự phòng trong lúc ảnh chưa tải xong.

Sửa màu không đạt như thế nào

Khi một cặp màu không đạt, hiếm khi bạn phải đổi cả bảng màu:

  • Chỉnh độ sáng, giữ nguyên tông màu. Làm tối màu chữ (hoặc làm sáng màu nền) từng chút một cho tới khi đạt. Tông màu vẫn nhận ra được là màu thương hiệu. Công cụ kiểm tra tương phản trên nicetool.dev tự gợi ý màu gần nhất đạt chuẩn.
  • Tăng cỡ chữ. Nếu buộc phải giữ màu (ví dụ màu thương hiệu trên banner), chỉ dùng nó cho chữ từ 24px trở lên thì yêu cầu giảm xuống 3:1.
  • Đổi vai trò. Màu thương hiệu sáng không đạt khi làm màu chữ thường lại rất hợp làm nền với chữ tối bên trên.
  • Dùng hai token. Giữ brand-500 cho các mảng màu trang trí và thêm token brand-700 cho chữ và icon. Phần lớn design system cuối cùng đều tách như vậy.

Khi đổi màu, hãy chuyển nó sang đúng định dạng codebase đang dùng (HEX, RGB, HSL hay OKLCH) bằng công cụ Chuyển đổi màu để các design token luôn thống nhất.

Còn APCA và WCAG 3 thì sao?

Có thể bạn đã nghe về APCA (Accessible Perceptual Contrast Algorithm). Thuật toán này mô phỏng cảm nhận của mắt chính xác hơn công thức WCAG 2.x, ví dụ có tính đến độ đậm của font và phân biệt chữ sáng trên nền tối với chữ tối trên nền sáng. APCA đang được phát triển cho WCAG 3, phiên bản vẫn còn là bản nháp. Cho tới khi trở thành tiêu chuẩn chính thức, tỷ lệ WCAG 2.x vẫn là yêu cầu khi đánh giá và khi tuân thủ pháp lý. Bạn có thể thử APCA trong quá trình thiết kế, nhưng vẫn phải kiểm tra theo ngưỡng 4.5:1 và 3:1.

Checklist nhanh

  • Chữ nội dung và nhãn: từ 4.5:1 trở lên.
  • Chữ từ 24px (hoặc từ 18.66px in đậm): từ 3:1 trở lên.
  • Viền ô nhập, vòng focus, icon có ý nghĩa: 3:1 so với màu bên cạnh.
  • Kiểm tra cả trạng thái hover, focus, lỗi và disabled, không chỉ trạng thái mặc định.
  • Kiểm tra riêng giao diện sáng và tối.
  • Trộn màu trong suốt với nền trước khi đo.
  • Không bao giờ làm tròn 4.4x thành 4.5.

Độ tương phản là một trong những cải thiện khả năng tiếp cận rẻ nhất: không cần viết thêm code, chỉ cần chọn màu tốt hơn ngay từ đầu. Dán màu chữ và màu nền vào công cụ Kiểm tra độ tương phản màu để xem tỷ lệ, kết quả AA/AAA và màu gợi ý chỉ trong vài giây. Mọi thứ chạy ngay trong trình duyệt của bạn.

Tác giả

Nguyễn Thành Nam

Full-Stack Engineer · Backend & Platform

Kỹ sư full-stack với hơn 9 năm kinh nghiệm xây dựng API và nền tảng lưu lượng lớn bằng Go, NestJS, PHP (Laravel, Phalcon) và Node.js. Chuyên về thiết kế API GraphQL, hệ thống thời gian thực và tối ưu hiệu năng; xây dựng NICETOOL.dev cho cộng đồng lập trình viên.

Tự thử ngay

Bài viết liên quan