فاحص تباين الألوان
تحقق من نسبة تباين WCAG AA و AAA بين لوني المقدمة والخلفية
لون المقدمة (النص)
لون الخلفية
نموذج نص كبير (18px عريض)
نموذج نص عادي — هكذا سيظهر نص المحتوى على لون الخلفية الذي اخترته.
نسبة التباين
14.63:1
توافق WCAG
ناجح
عادي AA
ناجح
عادي AAA
ناجح
كبير AA
ناجح
كبير AAA
WCAG AA — نص عادي: 4.5:1, نص كبير (18px أو أكبر، أو 14px عريض): 3:1
WCAG AAA — نص عادي: 7:1, نص كبير (18px أو أكبر، أو 14px عريض): 4.5:1
ما هو فحص تباين الألوان؟
يقيس فحص تباين الألوان الفرق في الإضاءة بين لون المقدمة (عادةً النص) ولون الخلفية، ثم يعبّر عن النتيجة على شكل نسبة. نسبة 1:1 تعني أن اللونين متطابقان؛ أما 21:1 فهي الحد الأقصى وتمثل الأسود على الأبيض أو العكس. تُحدد إرشادات إمكانية وصول محتوى الويب (WCAG) التي يصدرها W3C الحد الأدنى لنسب التباين التي يجب أن يحققها النص حتى يتمكن الأشخاص ذوو الإعاقات البصرية أو اضطرابات تمييز الألوان من قراءته.
تأتي صيغة نسبة التباين من WCAG 2.x وتعتمد على الإضاءة النسبية، وهي مقياس لمدى سطوع اللون أمام العين البشرية. تُحسب الإضاءة النسبية بتحويل كل قناة sRGB إلى قيمة خطية (بإزالة تصحيح غاما) ثم ترجيح القنوات وفق معاملات ITU-R BT.709: 0.2126 للأحمر، و0.7152 للأخضر، و0.0722 للأزرق. يُسهم الأخضر بالنصيب الأكبر لأن العين البشرية أكثر حساسية له. وتُحسب النسبة بالصيغة (L1 + 0.05) / (L2 + 0.05)، حيث L1 هي إضاءة اللون الأفتح.
يُحدد WCAG مستويين للامتثال. يتطلب المستوى AA نسبة تباين لا تقل عن 4.5:1 للنص العادي الحجم و3:1 للنص الكبير (18px فأكبر، أو 14px بخط عريض). يرفع المستوى AAA السقف إلى 7:1 و4.5:1 على التوالي. كما أضاف WCAG 2.1 معيار النجاح 1.4.11 الذي يشترط نسبة 3:1 لعناصر واجهة المستخدم غير النصية كالحدود والأيقونات ومؤشرات التركيز.
لماذا تستخدم هذا الفاحص؟
الحكم على التباين بالعين مجرد تخمين غير موثوق. الألوان التي تبدو واضحة التمييز على شاشتك المعايَرة قد تبدو متشابهة على شاشة حاسوب محمول رديئة الجودة، أو في ضوء الشمس المباشر، أو لشخص مصاب بعمى الألوان الثنائي. توفر نسبة التباين الرقمية حكمًا قاطعًا بالنجاح أو الفشل وفق معيار WCAG دون الحاجة إلى تخمين.
حالات استخدام فاحص التباين
متطلبات نسبة التباين في WCAG
يلخص الجدول أدناه الحد الأدنى لنسب التباين التي يشترطها WCAG 2.1 لأنواع مختلفة من المحتوى ومستويات الامتثال. يُعرَّف النص الكبير بأنه 18px (24 CSS pixels) فأكبر بوزن عادي، أو 14px (18.66 CSS pixels) فأكبر بوزن عريض.
| المستوى | الحد الأدنى للنسبة | ينطبق على | ملاحظة |
|---|---|---|---|
| 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 |
الإضاءة النسبية و AA و AAA: شرح مفصل
يمر حساب نسبة التباين بثلاث مراحل: حساب الإضاءة لكل لون، واشتقاق النسبة، ومقارنتها بحدود WCAG.
أمثلة برمجية
احسب نسب تباين WCAG برمجيًا. كل مثال يُطبّق صيغة الإضاءة النسبية من WCAG 2.x وحساب نسبة التباين. تُختبر أزواج الأسود على الأبيض والنيلي على الأبيض للمقارنة.
// 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;
}