Kiểm Tra Độ Tương Phản Màu
Kiểm tra tỷ lệ tương phản WCAG AA và AAA giữa màu chữ và màu nền
Màu chữ (nền trước)
Màu nền
Mẫu văn bản lớn (18px đậm)
Mẫu văn bản thường — đây là cách nội dung chính của bạn sẽ hiển thị trên màu nền bạn đã chọn.
Tỷ lệ tương phản
14.63:1
Tuân thủ WCAG
Đạt
Thường AA
Đạt
Thường AAA
Đạt
Lớn AA
Đạt
Lớn AAA
WCAG AA — Văn bản thường: 4.5:1, Văn bản lớn (18px+ hoặc 14px đậm): 3:1
WCAG AAA — Văn bản thường: 7:1, Văn bản lớn (18px+ hoặc 14px đậm): 4.5:1
Kiểm Tra Độ Tương Phản Màu Là Gì?
Kiểm tra độ tương phản màu đo sự khác biệt về độ sáng giữa màu chữ (thường là văn bản) và màu nền, sau đó biểu thị kết quả dưới dạng tỷ lệ. Tỷ lệ 1:1 nghĩa là hai màu giống hệt nhau; 21:1 là giá trị tối đa, tương ứng với chữ đen trên nền trắng hoặc ngược lại. Hướng dẫn Truy Cập Nội Dung Web (WCAG) do W3C ban hành xác định tỷ lệ tương phản tối thiểu mà văn bản phải đáp ứng để người có thị lực kém hoặc khiếm khuyết về màu sắc có thể đọc được.
Công thức tính tỷ lệ tương phản đến từ WCAG 2.x và dựa trên độ sáng tương đối — một thước đo mức độ sáng mà màu sắc xuất hiện với mắt người. Độ sáng tương đối được tính bằng cách tuyến tính hóa từng kênh sRGB (loại bỏ gamma) và gia trọng các kênh theo hệ số ITU-R BT.709: 0,2126 cho đỏ, 0,7152 cho xanh lá, và 0,0722 cho xanh lam. Xanh lá đóng góp nhiều nhất vì mắt người nhạy cảm nhất với ánh sáng xanh lá. Tỷ lệ được tính là (L1 + 0,05) / (L2 + 0,05), trong đó L1 là độ sáng của màu sáng hơn.
WCAG xác định hai mức tuân thủ. Mức AA yêu cầu tỷ lệ tương phản tối thiểu 4,5:1 cho văn bản cỡ thường và 3:1 cho văn bản lớn (từ 18px trở lên, hoặc 14px đậm). Mức AAA nâng ngưỡng lên 7:1 và 4,5:1 tương ứng. WCAG 2.1 cũng bổ sung Tiêu Chí Thành Công 1.4.11, yêu cầu tỷ lệ 3:1 cho các thành phần giao diện không phải văn bản như đường viền, biểu tượng và chỉ báo tiêu điểm.
Tại Sao Dùng Công Cụ Kiểm Tra Tương Phản Này?
Kiểm tra tương phản bằng mắt thường không đáng tin cậy. Những màu trông rõ ràng trên màn hình được hiệu chỉnh của bạn có thể nhòe lại trên màn hình laptop chất lượng thấp, dưới ánh nắng trực tiếp, hoặc với người bị mù màu deuteranopia. Một tỷ lệ số loại bỏ sự phỏng đoán và cho bạn kết quả đạt/không đạt theo chuẩn WCAG.
Các Trường Hợp Sử Dụng Công Cụ Kiểm Tra Tương Phản
Yêu Cầu Tỷ Lệ Tương Phản WCAG
Bảng dưới đây tóm tắt tỷ lệ tương phản tối thiểu theo WCAG 2.1 cho các loại nội dung và mức tuân thủ khác nhau. Văn bản lớn được định nghĩa là từ 18px (24 CSS pixels) trở lên ở trọng lượng thường, hoặc từ 14px (18,66 CSS pixels) trở lên ở trọng lượng đậm.
| Mức | Tỷ lệ tối thiểu | Áp dụng cho | Ghi chú |
|---|---|---|---|
| AA Normal text | 4.5 : 1 | Body text, paragraphs, labels | Minimum for most UI text |
| AA Large text | 3.0 : 1 | Text >= 18px, or >= 14px bold | Minimum for headings |
| AA UI components | 3.0 : 1 | Borders, icons, focus indicators | Non-text contrast (WCAG 1.4.11) |
| AAA Normal text | 7.0 : 1 | Body text at highest standard | Enhanced readability |
| AAA Large text | 4.5 : 1 | Large text at highest standard | Enhanced for headings |
Giải Thích Độ Sáng, AA và AAA
Phép tính tỷ lệ tương phản có ba giai đoạn: tính độ sáng cho từng màu, suy ra tỷ lệ, và so sánh với ngưỡng WCAG.
Ví Dụ Code
Tính tỷ lệ tương phản WCAG theo chương trình. Mỗi ví dụ triển khai công thức độ sáng tương đối từ WCAG 2.x và phép tính tỷ lệ tương phản. Cùng một cặp màu đen-trên-trắng và indigo-trên-trắng được kiểm tra để so sánh.
// Calculate relative luminance per WCAG 2.x (sRGB)
function luminance(r, g, b) {
const [rs, gs, bs] = [r, g, b].map(c => {
c /= 255
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4)
})
return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs
}
// Contrast ratio between two RGB colors
function contrastRatio(fg, bg) {
const l1 = luminance(...fg)
const l2 = luminance(...bg)
const lighter = Math.max(l1, l2)
const darker = Math.min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
}
contrastRatio([0, 0, 0], [255, 255, 255]) // -> 21.0
contrastRatio([99, 102, 241], [255, 255, 255]) // -> 3.95
contrastRatio([29, 78, 216], [255, 255, 255]) // -> 6.06 (AA pass)def luminance(r: int, g: int, b: int) -> float:
"""Relative luminance per WCAG 2.x, ITU-R BT.709 coefficients."""
channels = []
for c in (r, g, b):
c /= 255
channels.append(c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4)
return 0.2126 * channels[0] + 0.7152 * channels[1] + 0.0722 * channels[2]
def contrast_ratio(fg: tuple, bg: tuple) -> float:
l1 = luminance(*fg)
l2 = luminance(*bg)
lighter, darker = max(l1, l2), min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
print(f"{contrast_ratio((0, 0, 0), (255, 255, 255)):.2f}") # -> 21.00
print(f"{contrast_ratio((99, 102, 241), (255, 255, 255)):.2f}") # -> 3.95
# Check WCAG AA for normal text
ratio = contrast_ratio((29, 78, 216), (255, 255, 255))
print(f"{ratio:.2f} — {'AA pass' if ratio >= 4.5 else 'AA fail'}") # -> 6.06 — AA passpackage main
import (
"fmt"
"math"
)
func linearize(c float64) float64 {
c /= 255
if c <= 0.04045 {
return c / 12.92
}
return math.Pow((c+0.055)/1.055, 2.4)
}
func luminance(r, g, b int) float64 {
return 0.2126*linearize(float64(r)) +
0.7152*linearize(float64(g)) +
0.0722*linearize(float64(b))
}
func contrastRatio(fgR, fgG, fgB, bgR, bgG, bgB int) float64 {
l1 := luminance(fgR, fgG, fgB)
l2 := luminance(bgR, bgG, bgB)
lighter := math.Max(l1, l2)
darker := math.Min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
}
func main() {
ratio := contrastRatio(0, 0, 0, 255, 255, 255)
fmt.Printf("%.2f\n", ratio) // -> 21.00
ratio = contrastRatio(29, 78, 216, 255, 255, 255)
fmt.Printf("%.2f\n", ratio) // -> 6.06 (AA pass for normal text)
}/* WCAG-safe color pairs — tested contrast ratios */
/* 12.63:1 — passes AAA normal text */
.high-contrast {
color: #1e293b; /* slate-800 */
background: #f8fafc; /* slate-50 */
}
/* 7.07:1 — passes AAA normal text */
.dark-theme-text {
color: #e2e8f0; /* slate-200 */
background: #0f172a; /* slate-900 */
}
/* 4.57:1 — passes AA normal, fails AAA */
.accent-on-white {
color: #1d4ed8; /* blue-700 */
background: #ffffff;
}
/* 2.14:1 — fails AA for text, but passes 3:1 for large text */
.muted-heading {
color: #94a3b8; /* slate-400 */
background: #ffffff;
}