Açık gri bir yardım metni ya da ince bir form çerçevesi tasarım dosyasında temiz görünür; güneş alan bir telefonda ya da az gören bir kullanıcının ekranında ise okunmaz hale gelir. Düşük kontrast, erişilebilirlik denetimlerinde en sık çıkan bulgulardan biridir ve düzeltmesi görece ucuzdur. Bu rehberde WCAG 2.2'nin kontrast kurallarını, oranın nasıl hesaplandığını, hangi araçlarla test edileceğini ve eşiği tutturamayan bir paleti, tasarım token'larını (design tokens) ve karanlık temayı nasıl düzelteceğinizi anlatıyoruz. Oranları WCAG'deki gibi ondalık noktayla yazıyoruz (4.5:1).
1. Kurallar: hangi oran nerede geçerli?
WCAG 2.2'de (W3C Tavsiyesi; güncel baskı 12 Aralık 2024) renk ve kontrast için dört temel başarı kriteri (success criterion, SC) vardır. Mevzuatın ve şartnamelerin çoğu AA seviyesini hedefler; W3C, AAA'nın bir sitenin tamamı için genel şart olarak istenmesini önermez.| Kriter | Seviye | Kapsam | Asgari oran |
|---|---|---|---|
| 1.4.3 Kontrast (Minimum) | AA | Normal metin ve metin görselleri | 4.5:1 |
| 1.4.3 Kontrast (Minimum) | AA | Büyük metin | 3:1 |
| 1.4.6 Kontrast (Geliştirilmiş) | AAA | Normal / büyük metin | 7:1 / 4.5:1 |
| 1.4.11 Metin Dışı Kontrast | AA | Arayüz bileşenlerini ve durumlarını tanıtan görsel bilgi, içeriği anlamak için gereken grafik parçaları | Komşu renklere karşı 3:1 |
| 1.4.1 Renk Kullanımı | A | Bilgi, eylem ve durum yalnızca renkle aktarılmamalı | Oran yok; renge ek ipucu gerekir |
Büyük metin (large scale text), en az 18 punto ya da en az 14 punto kalın metindir. CSS'te 1pt = 4/3 px olduğundan bu, normal kalınlıkta 24px, kalın yazıda yaklaşık 18.67px eder; W3C'nin açıklama belgesi (Understanding) kalın yazı için yaklaşık 18.5px der. Sınırdaki boyutlarda 4.5:1'i hedefleyin.
- Oranlar yuvarlanmaz: 4.499:1, 4.5:1 eşiğini geçmez; araç ekranda "4.5" gösterse bile.
- Fareyle üzerine gelindiğinde (hover) ya da klavye odağında görünen metin ile yer tutucu (placeholder) metin de kapsamdadır. Boyut, içeriğin sunulduğu haliyle değerlendirilir; kullanıcının tarayıcıda yakınlaştırması metni "büyük" yapmaz.
- Metin rengini tanımlayıp arka plan rengini tanımlamamak (ya da tersi) başarısızlık sayılır, çünkü kullanıcının varsayılan arka planı bilinemez.
- İstisnalar: logo ve marka adındaki metin, etkin olmayan (disabled) bileşenler, saf dekorasyon, kimseye görünmeyen metin ve önemli başka görsel içerik barındıran bir resmin parçası olan metin. 1.4.11'de ayrıca görünümünü tarayıcının belirlediği ve yazarın değiştirmediği kontroller de muaftır.
2. Kontrast oranı nasıl hesaplanır?
- Her kanalı 0-1 aralığına çevirin: c = değer / 255.
- Doğrusallaştırın: c ≤ 0.04045 ise c / 12.92, değilse ((c + 0.055) / 1.055)^2.4. Eski metinlerdeki 0.03928 eşiği, W3C'ye göre sonucu pratikte değiştirmez.
- Bağıl parlaklığı (relative luminance) bulun: L = 0.2126 R + 0.7152 G + 0.0722 B.
- Oranı hesaplayın: (L1 + 0.05) / (L2 + 0.05). L1 açık rengin parlaklığıdır; sonuç 1:1 ile 21:1 arasındadır.
Aşağıdaki betik aynı hesabı token çiftlerinize uygular ve başarısız bir çift varsa 1 koduyla çıkar; doğrudan CI hattınıza eklenebilir:
Python:
import sys
def channel(v8):
c = v8 / 255
return c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4
def luminance(hex_color):
h = hex_color.lstrip("#")
r, g, b = (int(h[k:k + 2], 16) for k in (0, 2, 4))
return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b)
def contrast(fg, bg):
light, dark = sorted((luminance(fg), luminance(bg)), reverse=True)
return (light + 0.05) / (dark + 0.05)
# (çift, ön plan, arka plan, asgari oran)
PAIRS = [
("light text-muted / surface", "#767676", "#FFFFFF", 4.5),
("light text-muted / card", "#767676", "#F5F5F5", 4.5),
("light border-input / surface", "#949494", "#FFFFFF", 3.0),
("dark text-muted / surface", "#757575", "#121212", 4.5),
]
failed = 0
for name, fg, bg, minimum in PAIRS:
ratio = contrast(fg, bg)
ok = ratio >= minimum # yuvarlama yok
failed += not ok
print(f"{'OK ' if ok else 'FAIL'} {name}: {ratio:.2f}:1 (min {minimum}:1)")
sys.exit(1 if failed else 0)
3. Test akışı ve araçlar
- Chrome DevTools: Inspect modundaki ipucu kutusu oranı gösterir; renk seçicideki Contrast ratio bölümü AA ve AAA çizgilerini çizip eşiği geçen bir renk önerir; CSS Overview panelinin Colors bölümü kontrast sorunlarını listeler.
- Firefox Erişilebilirlik Denetçisi (Accessibility Inspector): Check for issues menüsündeki Contrast seçeneği sorunlu öğeleri süzer; gradyan gibi karmaşık zeminlerdeki metin için tek değer yerine bir oran aralığı gösterir.
- Lighthouse ve axe: Lighthouse'un erişilebilirlik denetimi axe-core üzerinde çalışır. axe CLI ile birkaç sayfayı yalnızca kontrast kuralıyla tarayabilir ve ihlal bulunduğunda CI adımını durdurabilirsiniz (aşağıdaki örnek).
- Tasarım ve bağımsız araçlar: Figma eklentileri (örneğin Stark), WebAIM Contrast Checker ve ekrandaki herhangi bir pikseli damlalıkla ölçen ücretsiz Colour Contrast Analyser (CCA; TPGi/Vispero, Windows ve macOS).
Bash:
npm install -g @axe-core/cli
axe https://example.com/, https://example.com/giris --rules color-contrast --exit
# ChromeDriver sürümü kurulu Chrome ile uyuşmazsa:
npx browser-driver-manager install chrome
- Sayfayı Tab tuşuyla gezin; odak halkası her zeminde görünüyor mu, bakın.
- Hover, seçili, ziyaret edilmiş (visited), hata ve başarı durumlarını; toast, tooltip ve doğrulama mesajlarını tek tek tetikleyin.
- Görsel ve gradyan üzerindeki metni en düşük kontrastlı noktada ölçün. W3C'nin F83 hata tekniği de aynı hızlı kontrolü tarif eder: Koyu metni görselin en koyu, açık metni en açık bölgesiyle karşılaştırın.
- Yarı saydam metni ekranda oluşan renkle hesaplayın: rgba(0, 0, 0, 0.5) beyaz zeminde yaklaşık #808080 olarak görünür; oran 4:1 civarındadır ve 4.5:1 eşiğini karşılamaz.
- Hepsini karanlık temada tekrarlayın.
4. Kontrastı yetersiz paleti düzeltmek
Renk tonunu değil, açıklığı değiştirin. WCAG oranı yalnızca iki rengin bağıl parlaklığına bakar; renk tonu (hue) ancak parlaklığı değiştirdiği ölçüde etkilidir. HSL'nin L değeri (lightness) burada yanıltır: #FFFF00 ile #0000FF ikisi de %50 açıklıktadır, ama beyaza karşı 1.07:1 ve 8.59:1 verir. OKLCH'nin L kanalı algıya çok daha yakındır (yaklaşık 0.97 ve 0.45). Rengi OKLCH'ye çevirin, tonu ve kromayı (chroma) sabit tutup L'yi adım adım değiştirin ve her adımda WCAG oranını hesaplayın. L'yi çok değiştirince doygun bir renk sRGB renk gamının (gamut) dışına çıkabilir; bu durumda kromayı biraz düşürün, yoksa tarayıcı rengi gama sığdırmak için değiştirir ve ekrandaki renk ölçtüğünüz renk olmaz.Önceden doğrulanmış ton skalaları kurun. Her renk ailesi için 50-900 gibi bir skala oluşturun ve hangi basamak çiftlerinin hangi eşiği geçtiğini belgeleyin. ABD federal tasarım sistemi USWDS'nin "sihirli sayı" (magic number) kuralı iyi bir örnektir: 0 (beyaz) ile 100 (siyah) arasındaki renk dereceleri (grade) belirli parlaklık bantlarına karşılık gelir; derece farkı 40 ve üzeri büyük metin için AA, 50 ve üzeri normal metin için AA, 70 ve üzeri AAA sağlar. Bileşenleri ham renklere değil, text-muted ya da focus-ring gibi anlamsal token'lara bağlayın; böylece test ettiğiniz şey yüzlerce bileşen değil, birkaç düzine token çifti olur.
Marka ve durum renkleri. #007BFF zeminde beyaz metin 3.98:1'dir: 16px düğme yazısına yetmez, büyük metne ve 1.4.11'e yeter; #0062CC ile oran 5.80:1 olur. Marka rengi değiştirilemiyorsa daha koyu bir varyantını etkileşimli öğelerde kullanın. Durum renklerinde de aynı tuzak var: Beyaz metin #4CAF50 yeşilde 2.78:1 verir, #2E7D32'de 5.13:1. Turuncu zeminde ise WCAG 2 formülü koyu metni öne çıkarır: #F57C00 üzerinde beyaz 2.70:1, siyah 7.77:1 verir.
- Bağlantılar: Yalnızca renkle ayrılan metin içi bağlantılar için W3C'nin G183 tekniği, bağlantı rengiyle çevredeki metin arasında en az 3:1 ister; iki renk de zemine karşı ayrıca 4.5:1 sağlamalıdır. Gövde metni #333333 ise beyaz zeminde iki koşulu birlikte sağlayan renk yoktur; siyah gövde metniyle #0062CC çalışır (5.80:1 ve 3.62:1). En sağlam çözüm alt çizgidir.
- Odak göstergesi: :focus-visible ile kalın bir outline kullanın, outline-offset ile halkayı bileşenin dışına, sayfa zeminine taşıyın. Yalnızca box-shadow'a güvenmeyin: Windows kontrast temalarında (forced colors) box-shadow kaldırılır, outline ise sistem rengiyle çizilmeye devam eder. AAA seviyesindeki 2.4.13 Odak Görünümü'nün ölçütü (2 CSS piksel kalınlığındaki çevre çizgisi kadar alan, odaklı ve odaksız durum arasında 3:1 değişim) AA hedeflerken de iyi bir referanstır.
- Form alanları: #CCCCCC çerçeve beyazda 1.61:1'dir. #949494 3.03:1 ile geçer, #959595 ise 2.995:1 ile kalır. Aynı #949494, #F5F5F5 kartın üzerinde 2.78:1'e düşer; aşağıdaki token setinde bu yüzden #8A8A8A var. Yer tutucuyu etiket yerine kullanmayın; #999999 (2.85:1) gibi açık griler yer tutucu metnin ihtiyaç duyduğu 4.5:1'i karşılamaz.
- Görsel üzerindeki metin ve grafikler: Metnin arkasına koyu bir katman (scrim) ya da düz panel koyun ve en açık noktayı her kırılma noktasında (breakpoint) ölçün. Grafik çizgileri zemine karşı 3:1 sağlamalı; seriler etiket, desen ya da işaret şekliyle de ayrılmalı.
Kod:
:root {
color-scheme: light dark;
--surface: #FFFFFF;
--surface-raised: #F5F5F5;
--text: #1F1F1F; /* 16.48:1 / 15.12:1 */
--text-muted: #6B6B6B; /* 5.33:1 / 4.89:1 */
--border-input: #8A8A8A; /* 3.45:1 / 3.17:1 */
--link: #0062CC; /* 5.80:1 / 5.32:1 */
--focus-ring: #005FCC; /* 5.98:1 / 5.49:1 */
}
@media (prefers-color-scheme: dark) {
:root {
--surface: #121212;
--surface-raised: #2C2C2C;
--text: #E6E6E6; /* 15.01:1 / 11.19:1 */
--text-muted: #9E9E9E; /* 6.99:1 / 5.21:1 */
--border-input: #7A7A7A; /* 4.36:1 / 3.25:1 */
--link: #5AA9FF; /* 7.63:1 / 5.69:1 */
--focus-ring: #5AA9FF; /* 7.63:1 / 5.69:1 */
}
}
@media (prefers-contrast: more) {
:root { --text-muted: var(--text); --border-input: var(--text); }
}
body { background: var(--surface); color: var(--text); }
.card { background: var(--surface-raised); }
a { color: var(--link); text-decoration: underline; }
:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 2px; }
5. Karanlık mod: her çifti yeniden test edin
Karanlık tema, açık temanın ters çevrilmiş hali değildir. Saf siyah zeminde saf beyaz metin 21:1 ile en yüksek oranı verir, ama her zaman en rahat okunan seçenek değildir; bazı kullanıcılar, özellikle astigmatlı kişiler, harflerin çevresinde parlama (halation) gördüklerini belirtir. Material Design'ın temel karanlık teması zemin ve yüzeylerde saf siyah yerine #121212 koyu grisini kullanır; bu zeminde #E6E6E6 metin 15.01:1 verir.Kart, menü ve modal gibi yükseltilmiş (elevated) yüzeyler daha açık tondadır ve soluk metnin kontrastını düşürür: #8A8A8A, #121212 üzerinde 5.43:1, #1E1E1E üzerinde 4.83:1, #2C2C2C üzerinde 4.05:1 verir. Yukarıdaki token setinde koyu temanın soluk metni için bu yüzden #9E9E9E kullandık; #2C2C2C üzerinde bile 5.21:1 sağlar. Beyazda 5.80:1 veren #0062CC bağlantı rengi #121212 üzerinde 3.23:1'e düşer; koyu tema için #5AA9FF (7.63:1) gibi ayrı bir token tanımlayın. Odak halkasını, çerçeveleri, grafik ve durum renklerini de iki temada ve her yüzey seviyesinde ayrı ölçün. Token mimarisi için design system'ler yazımıza, erişilebilirliği tasarım kısıtı olarak ele alan 2026'da web tasarımı yazımıza göz atabilirsiniz.
6. WCAG 2'nin ötesi: APCA ve WCAG 3
WCAG 2 formülü basit ve tekrarlanabilir, ama yazı kalınlığını yalnızca "büyük metin" eşiğiyle hesaba katar, açık zemindeki koyu metinle koyu zemindeki açık metni aynı kefeye koyar ve koyu renk çiftlerinde algıyla uyuşmadığı için eleştirilir. Algıya dayalı APCA (Accessible Perceptual Contrast Algorithm) WCAG 3'ün 2021 tarihli taslaklarında yer aldı, Temmuz 2023 taslağında artık yoktu; W3C'nin 10 Eylül 2026 tarihli WCAG 3 çalışma taslağı (Working Draft) kontrast algoritmasının henüz belirlenmediğini söylüyor. Uygunluk bugün WCAG 2.x ile değerlendirilir; APCA'yı yalnızca ek bir tasarım girdisi olarak kullanın.7. Mevzuat: AB Erişilebilirlik Yasası ve Türkiye
Avrupa Birliği: Avrupa Erişilebilirlik Yasası (European Accessibility Act, EAA; Direktif (AB) 2019/882) 28 Haziran 2025'ten beri uygulanıyor. E-ticaret, tüketici bankacılığı, e-kitap, elektronik haberleşme (telefon) ve yolcu taşımacılığı hizmetlerini; bilgisayar, akıllı telefon ve self-servis terminal gibi ürünleri kapsıyor. AB'ye satış yapan AB dışı şirketler de etkilenebilir. Hizmet sunan mikro işletmeler (10'dan az kişi çalıştıran ve yıllık cirosu ya da bilanço toplamı 2 milyon avroyu aşmayan) hizmetlere ilişkin erişilebilirlik yükümlülüklerinden muaftır. Teknik referans genellikle EN 301 549'dur: V3.2.1 WCAG 2.1 AA'ya, Eylül 2026'da yayımlanan V4.1.1 ise WCAG 2.2 AA'ya dayanır; ikisinde de 1.4.3 ve 1.4.11'in karşılığı olan 9.1.4.3 ve 9.1.4.11 maddeleri vardır. V4.1.1 doğrudan EAA için hazırlandı, ancak uygunluk karinesi (presumption of conformity) sağlaması için bu direktif kapsamında AB Resmî Gazetesi'nde atıf yapılması gerekir. Ulusal uygulamaları hukuk danışmanınızla kontrol edin.Türkiye: 21 Haziran 2025 tarihli Resmî Gazete'de yayımlanan 2025/10 sayılı Cumhurbaşkanlığı Genelgesi, web siteleri ve mobil uygulamaların Aile ve Sosyal Hizmetler Bakanlığı'nın "Kontrol Listesi - A Seviyesi" ile WCAG 2.2'ye uygun hale getirilmesini istiyor. Kamu kurumları, üniversiteler, belediyeler, bankalar, özel hastaneler ve genelgede sayılan diğer kuruluşlara bir yıl, 6563 sayılı Kanun kapsamında elektronik ticaret yapan hizmet sağlayıcılara iki yıl süre verildi. Bakanlığın 2026 tarihli kontrol listesi WCAG 2.2'nin A seviyesindeki 31 başarı kriterine dayanıyor: 1.4.1 Renk Kullanımı listede var, AA seviyesindeki 1.4.3 ve 1.4.11 yok. Biz yine de AA kontrastını hedeflemenizi öneriyoruz; EAA kapsamındaki ürün ve hizmetlerde pratikte beklenen düzey de budur. Liste ve süreler değişebileceği için Bakanlığın duyuru sayfasını ve Resmî Gazete'deki güncel metni kontrol edin.
8. Kontrol listesi ve sık yapılan hatalar
- Gövde, yardımcı, tablo ve yer tutucu metin en az 4.5:1; büyük metin en az 3:1.
- Form çerçeveleri, onay kutuları, ikon düğmeler, grafik çizgileri ve odak göstergesi komşu renklere karşı en az 3:1.
- Bağlantı, hata ve zorunlu alan bilgisi renge ek olarak alt çizgi, metin ya da ikonla veriliyor.
- Görsel ve gradyan üzerindeki metin en kötü noktasında ölçüldü.
- Tüm token çiftleri iki temada ve her yüzey seviyesinde CI'da test ediliyor.
Sık sorulan sorular
4.5:1 oranı AA için mi, AAA için mi?AA için. Normal metinde 4.5:1, büyük metinde 3:1 AA'dır; AAA'da bu değerler 7:1 ve 4.5:1'e çıkar.
Devre dışı (disabled) düğmeler kontrast şartından muaf mı?
Evet, etkin olmayan bileşenler 1.4.3 ve 1.4.11'den muaftır. Yine de düğmenin neden pasif olduğunu yanındaki bir metinle açıklamak iyi bir uygulamadır.
Logomuzun renklerini değiştirmemiz gerekir mi?
Hayır, logo ve marka adındaki metin muaftır. Ancak kurumsal kimlik gereği değil de tasarım tercihiyle soluk gösterilen bir logo (örneğin üzerine gelinene kadar gri duran referans logoları) muaf sayılmaz; aynı renkler gövde metninde ya da düğmede kullanıldığında da normal kurallar geçerlidir.
Karanlık temada eşikler farklı mı?
Hayır. WCAG 2 iki temada da aynı oranları ister: normal metinde 4.5:1, büyük metinde ve arayüz bileşenlerinde 3:1. Fark, her renk çiftini koyu zeminde ve her yüzey seviyesinde yeniden ölçmeniz gerekmesidir.
Otomatik test kontrast sorunlarının hepsini yakalar mı?
Hayır. Görsel ve gradyan üzerindeki metin, hover ve odak durumları, saydamlık ve dinamik içerik manuel kontrol ister.
APCA ile test edip WCAG 2'yi atlayabilir miyiz?
Hayır. APCA bir standart değildir ve WCAG 3 taslağı henüz bir kontrast algoritması seçmedi; uygunluk WCAG 2.x oranlarıyla değerlendirilir.
Sonuç ve sonraki adımlar
Kontrast hatalarının çoğu tek bir kötü renkten değil, hiç test edilmemiş renk çiftlerinden çıkar. Önce token çiftlerinizi yukarıdaki betikle test edin, sonra en çok kullanılan şablonları axe ile tarayıp klavyeyle gezin, en son karanlık temayı aynı listeyle doğrulayın. Semantik yapı, form etiketleri ve klavye erişimi için HTML 2026 yazımıza bakabilirsiniz. Kurumsal bir portalı ya da operatör arayüzünü baştan erişilebilir token'larla kurmayı planlıyorsanız AIOR'un kurumsal ve endüstriyel yazılım sayfasında çalışma kapsamını inceleyebilirsiniz.Light gray helper text or a hairline input border can look clean in a design file and become unreadable on a phone in sunlight or for a user with low vision. Low contrast is one of the most frequent findings in accessibility audits, and one of the cheapest to fix. This guide covers the WCAG 2.2 contrast rules, how the ratio is calculated, which tools to test with, and how to repair a failing palette, design tokens and a dark theme.
1. The rules: which ratio applies where
WCAG 2.2 (a W3C Recommendation, current edition 12 December 2024) has four core success criteria (SC) for color and contrast. Most regulations and procurement specs target level AA, and W3C advises against requiring AAA as a blanket policy for whole sites.| Criterion | Level | Applies to | Minimum |
|---|---|---|---|
| 1.4.3 Contrast (Minimum) | AA | Normal text and images of text | 4.5:1 |
| 1.4.3 Contrast (Minimum) | AA | Large text | 3:1 |
| 1.4.6 Contrast (Enhanced) | AAA | Normal / large text | 7:1 / 4.5:1 |
| 1.4.11 Non-text Contrast | AA | Visual information needed to identify UI components and their states; graphics needed to understand content | 3:1 against adjacent colors |
| 1.4.1 Use of Color | A | Information, actions and states must not rely on color alone | No ratio; add a non-color cue |
Large text is at least 18 point, or 14 point bold. At 1pt = 4/3 CSS px, that is 24px regular or about 18.67px bold; W3C's Understanding document rounds the bold figure to approximately 18.5px. Near the boundary, design for 4.5:1.
- Ratios are not rounded: 4.499:1 fails 4.5:1, even if a tool displays "4.5".
- Text revealed on hover or keyboard focus is in scope, and so is placeholder text. Size is judged as the content is delivered, so browser zoom does not make body text "large".
- Setting a text color without a background color, or the reverse, is a failure: the user's default background is unknown.
- Exceptions: text in logos and brand names, inactive (disabled) components, pure decoration, text that is not visible to anyone, and text inside a picture with significant other visual content. Under 1.4.11, controls whose appearance is determined by the browser and not modified by the author are also exempt.
2. How the contrast ratio is calculated
- Scale each channel to 0-1: c = value / 255.
- Linearize: if c ≤ 0.04045 then c / 12.92, else ((c + 0.055) / 1.055)^2.4. Older texts use 0.03928; W3C notes the difference has no practical effect.
- Compute relative luminance: L = 0.2126 R + 0.7152 G + 0.0722 B.
- Compute the ratio: (L1 + 0.05) / (L2 + 0.05), with L1 the lighter color. Results run from 1:1 to 21:1.
The script below applies the same math to your token pairs and exits with code 1 if any pair fails, so it can go straight into CI:
Python:
import sys
def channel(v8):
c = v8 / 255
return c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4
def luminance(hex_color):
h = hex_color.lstrip("#")
r, g, b = (int(h[k:k + 2], 16) for k in (0, 2, 4))
return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b)
def contrast(fg, bg):
light, dark = sorted((luminance(fg), luminance(bg)), reverse=True)
return (light + 0.05) / (dark + 0.05)
# (pair, foreground, background, minimum ratio)
PAIRS = [
("light text-muted / surface", "#767676", "#FFFFFF", 4.5),
("light text-muted / card", "#767676", "#F5F5F5", 4.5),
("light border-input / surface", "#949494", "#FFFFFF", 3.0),
("dark text-muted / surface", "#757575", "#121212", 4.5),
]
failed = 0
for name, fg, bg, minimum in PAIRS:
ratio = contrast(fg, bg)
ok = ratio >= minimum # no rounding
failed += not ok
print(f"{'OK ' if ok else 'FAIL'} {name}: {ratio:.2f}:1 (min {minimum}:1)")
sys.exit(1 if failed else 0)
3. Testing workflow and tools
- Chrome DevTools: the Inspect tooltip shows the ratio; the color picker's Contrast ratio section draws AA and AAA lines and suggests a passing color; the CSS Overview panel lists contrast issues under Colors.
- Firefox Accessibility Inspector: the Contrast option under Check for issues filters failing elements, and text on complex backgrounds such as gradients gets a ratio range instead of a single value.
- Lighthouse and axe: Lighthouse's accessibility audit runs on axe-core. The axe CLI can scan several pages with only the contrast rule and stop the CI step when it finds violations (example below).
- Design and standalone tools: Figma plugins (Stark, for example), the WebAIM Contrast Checker, and the free Colour Contrast Analyser (CCA, from TPGi/Vispero, for Windows and macOS), whose eyedropper samples any pixel on screen.
Bash:
npm install -g @axe-core/cli
axe https://example.com/, https://example.com/login --rules color-contrast --exit
# if ChromeDriver does not match your installed Chrome:
npx browser-driver-manager install chrome
- Tab through the page and check that the focus ring is visible on every background.
- Trigger hover, selected, visited, error and success states, plus toasts, tooltips and validation messages.
- Measure text on images and gradients at the lowest-contrast point. W3C's failure technique F83 describes the same quick check: compare dark text with the darkest part of the image and light text with the lightest part.
- Calculate semi-transparent text with the composited color: rgba(0, 0, 0, 0.5) on white renders as roughly #808080, about 4:1, which fails 4.5:1.
- Repeat everything in the dark theme.
4. Fixing a failing palette
Change lightness, not hue. The WCAG ratio looks only at the relative luminance of the two colors; hue matters only as far as it changes luminance. HSL lightness is misleading here: #FFFF00 and #0000FF both sit at 50%, yet give 1.07:1 and 8.59:1 against white. OKLCH lightness tracks perception far better (about 0.97 and 0.45). Convert the color to OKLCH, hold hue and chroma, step L, and compute the WCAG ratio at each step. Large L changes can push a saturated color outside the sRGB gamut; reduce chroma a little when that happens, otherwise the browser has to squeeze the color into gamut and what appears on screen is not the color you measured.Build pre-validated tonal scales. Create a 50-900 scale for each color family and document which step pairs meet which threshold. The "magic number" in the U.S. Web Design System (USWDS) is a good model: its color grades run from 0 (white) to 100 (black) and map to luminance bands, so a grade difference of 40 or more gives AA for large text, 50 or more AA for normal text, and 70 or more AAA. Bind components to semantic tokens such as text-muted or focus-ring rather than raw colors; you then test a few dozen token pairs instead of hundreds of components.
Brand and status colors. White text on #007BFF is 3.98:1, too low for a 16px button label but enough for large text and 1.4.11; #0062CC gives 5.80:1. If the brand color is fixed, use a darker shade for interactive elements. Status colors hide the same trap (white on #4CAF50 is 2.78:1, on #2E7D32 5.13:1), and on orange WCAG 2 favors dark text: on #F57C00, white gives 2.70:1 and black 7.77:1.
- Links: for inline links distinguished by color alone, W3C technique G183 calls for at least 3:1 between the link color and the surrounding text, and both colors still need 4.5:1 against the background. With #333333 body text on white, no color meets both; with black body text, #0062CC works (5.80:1 and 3.62:1). Underlines are the robust fix.
- Focus indicators: use a thick outline on :focus-visible and move it off the component onto the page background with outline-offset. Do not rely on box-shadow alone: forced colors mode (Windows contrast themes) removes box-shadow but keeps the outline, drawn in a system color. The AAA criterion 2.4.13 Focus Appearance (an area at least as large as a 2 CSS px thick perimeter, with a 3:1 change between focused and unfocused states) is a good yardstick even when you target AA.
- Form fields: a #CCCCCC border on white is 1.61:1. #949494 passes at 3.03:1; #959595 fails at 2.995:1. The same #949494 drops to 2.78:1 on a #F5F5F5 card, which is why the token set below uses #8A8A8A. Do not use placeholders as labels, and if you show one, note that light grays such as #999999 (2.85:1) fail the 4.5:1 that placeholder text needs.
- Text on images and charts: put a dark scrim or a solid panel behind text and measure the lightest point at every breakpoint. Chart lines need 3:1 against the background, and series should also differ by label, pattern or marker shape.
Kod:
:root {
color-scheme: light dark;
--surface: #FFFFFF;
--surface-raised: #F5F5F5;
--text: #1F1F1F; /* 16.48:1 / 15.12:1 */
--text-muted: #6B6B6B; /* 5.33:1 / 4.89:1 */
--border-input: #8A8A8A; /* 3.45:1 / 3.17:1 */
--link: #0062CC; /* 5.80:1 / 5.32:1 */
--focus-ring: #005FCC; /* 5.98:1 / 5.49:1 */
}
@media (prefers-color-scheme: dark) {
:root {
--surface: #121212;
--surface-raised: #2C2C2C;
--text: #E6E6E6; /* 15.01:1 / 11.19:1 */
--text-muted: #9E9E9E; /* 6.99:1 / 5.21:1 */
--border-input: #7A7A7A; /* 4.36:1 / 3.25:1 */
--link: #5AA9FF; /* 7.63:1 / 5.69:1 */
--focus-ring: #5AA9FF; /* 7.63:1 / 5.69:1 */
}
}
@media (prefers-contrast: more) {
:root { --text-muted: var(--text); --border-input: var(--text); }
}
body { background: var(--surface); color: var(--text); }
.card { background: var(--surface-raised); }
a { color: var(--link); text-decoration: underline; }
:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 2px; }
5. Dark mode: re-test every pair
A dark theme is not an inverted light theme. Pure white on pure black gives the maximum 21:1 but is not always the most comfortable choice; some readers, particularly people with astigmatism, report a glow around letters (halation). Material Design's baseline dark theme uses #121212 dark gray instead of black for backgrounds and surfaces; on it, #E6E6E6 text gives 15.01:1.Elevated surfaces such as cards, menus and dialogs are lighter, which weakens muted text: #8A8A8A gives 5.43:1 on #121212, 4.83:1 on #1E1E1E and 4.05:1 on #2C2C2C. That is why the token set above uses #9E9E9E for dark muted text; it keeps 5.21:1 even on #2C2C2C. A #0062CC link that reaches 5.80:1 on white drops to 3.23:1 on #121212, so define a separate dark token such as #5AA9FF (7.63:1). Measure focus rings, borders, chart and status colors in both themes and on every surface level too. We covered token architecture in our design systems article and accessibility as a design constraint in Web design in 2026.
6. Beyond WCAG 2: APCA and WCAG 3
The WCAG 2 formula is simple and repeatable, but it accounts for font weight only through the large-text cutoff, treats dark-on-light and light-on-dark alike, and is criticized for misjudging dark color pairs. The perceptual APCA model (Accessible Perceptual Contrast Algorithm) appeared in the 2021 WCAG 3 drafts and was gone from the July 2023 draft; the WCAG 3 Working Draft of 10 September 2026 says the contrast algorithm is yet to be determined. Conformance is judged against WCAG 2.x, so treat APCA as an extra design input at most.7. Regulation: the European Accessibility Act and Türkiye
European Union: the European Accessibility Act (EAA, Directive (EU) 2019/882) has applied since 28 June 2025. It covers services such as e-commerce, consumer banking, e-books, electronic communications (telephony) and passenger transport, and products such as computers, smartphones and self-service terminals; non-EU companies selling into the EU can be affected too. Microenterprises providing services (fewer than 10 employees and an annual turnover or balance sheet total of at most EUR 2 million) are exempt from the accessibility requirements for services. The usual technical reference is EN 301 549: V3.2.1 builds on WCAG 2.1 AA and V4.1.1 (September 2026) on WCAG 2.2 AA, and both carry 1.4.3 and 1.4.11 as clauses 9.1.4.3 and 9.1.4.11. V4.1.1 was written for the EAA, but it gives a presumption of conformity only once it is cited in the Official Journal of the EU under that directive. Check national implementation with legal counsel.Türkiye: Presidential Circular 2025/10, published in the Official Gazette (Resmî Gazete) on 21 June 2025, requires websites and mobile apps to be aligned with the Ministry of Family and Social Services' Level A checklist ("Kontrol Listesi - A Seviyesi") and WCAG 2.2. Public bodies, universities, municipalities, banks, private hospitals and the other organizations listed in the circular were given one year; e-commerce service providers under Law No. 6563 were given two years. The ministry's 2026 checklist is built on the 31 Level A criteria of WCAG 2.2: 1.4.1 Use of Color is in, while the AA criteria 1.4.3 and 1.4.11 are not. We still recommend designing to AA contrast; it is also the level expected in practice for products and services covered by the EAA. The checklist and deadlines can change, so check the ministry's announcement (in Turkish) and the current text in the Official Gazette.
8. Checklist and common mistakes
- Body, helper, table and placeholder text at 4.5:1 or more; large text at 3:1 or more.
- Input borders, checkboxes, icon buttons, chart lines and focus indicators at 3:1 or more against adjacent colors.
- Links, errors and required fields marked by underline, text or icon as well as color.
- Text on images and gradients measured at its worst point.
- Every token pair tested in CI, in both themes and on every surface level.
FAQ
Is 4.5:1 the AA or the AAA requirement?AA. Normal text needs 4.5:1 and large text 3:1 at AA; AAA raises these to 7:1 and 4.5:1.
Are disabled buttons exempt from contrast rules?
Yes, inactive components are exempt from 1.4.3 and 1.4.11. It is still good practice to explain next to the button why it is disabled.
Do we have to change our logo colors?
No, text in logos and brand names is exempt. A logo shown faded by design choice rather than brand rules (for example, client logos that stay gray until hovered) is not exempt, and the same colors used for body text or buttons must meet the normal rules.
Are the thresholds different in dark mode?
No. WCAG 2 applies the same ratios in both themes: 4.5:1 for normal text, 3:1 for large text and UI components. The difference is that every pair has to be measured again on the dark background and on each surface level.
Will automated testing catch every contrast problem?
No. Text on images and gradients, hover and focus states, transparency and dynamic content need manual checks.
Can we test with APCA and skip WCAG 2?
No. APCA is not a standard and the WCAG 3 draft has not chosen a contrast algorithm; conformance is assessed with the WCAG 2.x ratios.