Kontras adalah beda terang antara teks dan latarnya, dan beda terang itulah yang membuat teks terbaca, bukan warnanya. Bayangin papan tulis di kelas. Tulisan kapur putih di papan hitam terbaca dari bangku paling belakang. Tulisan spidol kuning di papan putih, walau warnanya cerah, harus didekati dulu. Kuning dan putih sama-sama terang; beda terangnya kecil. Di web, beda terang ini diukur dengan satu angka: rasio kontras.
🧪 Tebakan cepat: Bu Sri sempat ingin tombol pesannya memakai hijau yang lebih cerah,
#22c55e, dengan teks putih. Hijaunya menyala dan teksnya putih bersih. Lolos standar WCAG? Jawabannya ada di bagian "Menghitung rasio kontras".
Rasio kontras dan batasnya
Rasio kontras berjalan dari 1:1 (warna sama persis) sampai 21:1 (hitam di atas putih). WCAG 2.2, standar aksesibilitas web dari W3C, menetapkan batasnya:
| Yang diukur | Kriteria | Level AA | Level AAA |
|---|---|---|---|
| Teks biasa | 1.4.3 dan 1.4.6 | 4,5:1 | 7:1 |
| Teks besar: minimal 24px, atau minimal 18,66px tebal | 1.4.3 dan 1.4.6 | 3:1 | 4,5:1 |
| Komponen UI dan grafis: garis tombol, cincin fokus, ikon | 1.4.11 | 3:1 |
- WCAG menulis ukuran teks besar dalam point: 18pt, atau 14pt tebal. Di CSS, 1pt = 1,333px, jadi 24px dan 18,66px.
- AA adalah level yang biasa diminta. AAA lebih ketat.
- Yang dibebaskan: komponen nonaktif (tombol yang gak bisa ditekan), hiasan murni, dan teks di dalam logo.
Menghitung rasio kontras
Rasionya dihitung dari luminans relatif (L): terang sebuah warna, dari 0 (hitam) sampai 1 (putih). Langkahnya, untuk hijau Bu Sri #15803d:
- Tiap kanal dibagi 255: R 21 jadi 0,0824, G 128 jadi 0,5020, B 61 jadi 0,2392.
- Tiap nilai c diubah: kalau c ≤ 0,04045 maka c ÷ 12,92, kalau lebih maka ((c + 0,055) ÷ 1,055) pangkat 2,4. Hasilnya R 0,00750, G 0,21586, B 0,04667.
- L = 0,2126 × R + 0,7152 × G + 0,0722 × B = 0,00159 + 0,15438 + 0,00337 = 0,1593.
- Rasio = (L terang + 0,05) ÷ (L gelap + 0,05). Putih punya L = 1, jadi (1 + 0,05) ÷ (0,1593 + 0,05) = 5,02:1.
Hijau mendapat bobot 0,7152 karena mata paling peka ke hijau. Rumus yang sama dalam JavaScript, yang juga dipakai Lab:
function luminance(hex) {
const [r, g, b] = [1, 3, 5].map((i) => {
const c = parseInt(hex.slice(i, i + 2), 16) / 255;
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
});
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
function contrast(a, b) {
const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);
return (hi + 0.05) / (lo + 0.05);
}
console.log(contrast("#ffffff", "#15803d").toFixed(2)); // "5.02"
console.log(contrast("#ffffff", "#22c55e").toFixed(2)); // "2.28"
luminancemenjalankan langkah 1 sampai 3 untuk satu warna hex.contrastmengurutkan dua luminans supaya yang terang di atas, lalu menjalankan langkah 4.- WCAG 2.0 dan 2.1 menulis 0,03928, bukan 0,04045. Untuk warna 8-bit hasilnya sama persis.
Jadi jawaban tebakannya: gak lolos. #22c55e punya L = 0,4108, jadi rasionya (1,05) ÷ (0,4608) = 2,28:1, jauh di bawah 4,5:1. Hijau yang cerah itu terang, dan teks putih juga terang; beda terangnya kecil, seperti spidol kuning di papan putih. Itu jawaban tebakannya. Dan jangan membulatkan ke atas: 4,49:1 tetap gagal.
Semua pasangan di halaman Bu Sri
Tiap teks diukur terhadap latar tepat di belakangnya:
| Teks atau komponen | Warna di atas latar | Ukuran | Rasio | Batas | Hasil |
|---|---|---|---|---|---|
| Judul "Homestay Kabut" | #1f2937 di #fff |
22px tebal (besar) | 14,68:1 | 3:1 | lolos |
| Kamar, check-in | #475569 di #fff |
16px | 7,58:1 | 4,5:1 | lolos |
| Label tombol pesan | white di #15803d |
16px tebal | 5,02:1 | 4,5:1 | lolos |
| Catatan "Gratis batal..." | #475569 di #fff |
14px | 7,58:1 | 4,5:1 | lolos |
| Teks di luar kartu | #475569 di #f1f5f9 |
14px | 6,92:1 | 4,5:1 | lolos |
| Garis tombol malam | #475569 di #fff |
komponen | 7,58:1 | 3:1 | lolos |
| Latar tombol pesan | #15803d di #fff |
komponen | 5,02:1 | 3:1 | lolos |
- Label tombol 16px tebal belum termasuk teks besar (butuh 18,66px tebal), jadi tetap 4,5:1.
- Kalau catatan diberi abu-abu pucat sewarna kabut,
#94a3b8, rasionya cuma 2,56:1 dan gagal.#64748b(4,76:1) atau#475569(7,58:1) lolos. - Garis kartu
#e2e8f0cuma 1,23:1 dari putih, dan itu boleh: kartu bukan komponen yang bisa ditekan, jadi garisnya hiasan.
Jangan cuma mengandalkan warna
Sebagian orang sulit membedakan merah dan hijau. Survei besar yang dirangkum Birch (2012) menemukan sekitar 8% pria dan 0,4% wanita keturunan Eropa punya buta warna merah-hijau, dan 4% sampai 6,5% pria di populasi Tionghoa dan Jepang. WCAG kriteria 1.4.1 (level A) meminta warna gak jadi satu-satunya cara menyampaikan informasi.
Contohnya di halaman Bu Sri: tanggal yang tersedia diberi titik hijau #15803d, tanggal penuh titik merah #b91c1c. Disimulasikan untuk deuteranopia (tanpa sel kerucut hijau) dengan matriks Machado, Oliveira, dan Fernandes (2009) di ruang RGB linear, hijau jadi #756c42 dan merah jadi #776a12: dua-duanya cokelat zaitun, rasio di antara keduanya 1,03:1. Tamu itu gak bisa tahu tanggal 14 penuh. Perbaikannya: tulis "penuh" dan coret tanggalnya, jadi informasinya terbaca tanpa warna.
Kontras terang justru selamat: teks putih di atas hijau yang disimulasikan masih 5,28:1. Itu alasan kontras diukur dari terang, bukan dari hue. Chrome DevTools bisa menyimulasikan ini di panel Rendering, bagian "Emulate vision deficiencies".
Latihan: tiga level teks
Di latihan, tiga teks di atas latar putih terlalu pucat: #D1D5DB (1,47:1) dan #E5E7EB (1,24:1). Ganti dengan abu-abu yang lolos:
.heading { color: #111827; } /* 17,74:1 */
.body { color: #374151; } /* 10,31:1 */
.caption { color: #6B7280; } /* 4,83:1, masih di atas 4,5:1 */
#111827paling gelap untuk judul,#374151untuk isi,#6B7280untuk keterangan kecil.<small>membuat teksnya lebih kecil dari 16px, jadi batasnya tetap 4,5:1, dan#6B7280lolos dengan sedikit sisa.
📌 Bagaimana dengan APCA?
APCA adalah metode kontras baru yang sedang dibahas untuk WCAG 3. Ia memperhitungkan ukuran dan tebal huruf dengan lebih teliti. Tapi WCAG 3 masih draf, bukan standar. Audit aksesibilitas dan aturan hukum hari ini memakai rasio WCAG 2.2.
⚠️ Jebakan yang sering ketemu
- Warna cerah dikira kontras tinggi.
#22c55emenyala, tapi dengan teks putih cuma 2,28:1. Ukur, jangan ditebak. - Placeholder dibuat pucat. Placeholder juga teks yang harus dibaca, jadi tetap 4,5:1.
- Membulatkan ke atas. 4,49:1 gagal, walau terlihat seperti 4,5.
- Status cuma dari warna. Titik hijau dan merah tanpa tulisan hilang bagi pengguna buta warna. Tambahkan teks atau ikon.
- Teks di atas foto. Foto kabut yang putih membuat teks putih hilang di sebagian tempat. Beri lapisan gelap di belakang teks, lalu ukur di bagian foto yang paling terang.
TL;DR: Rasio kontras = (L terang + 0,05) ÷ (L gelap + 0,05), dengan L luminans relatif dari 0 sampai 1. WCAG 2.2 AA: teks biasa 4,5:1, teks besar (24px, atau 18,66px tebal) 3:1, komponen UI 3:1; AAA: 7:1. Di halaman Bu Sri: putih di #15803d 5,02:1 lolos, #22c55e 2,28:1 gagal, catatan #475569 7,58:1 lolos. Jangan bulatkan ke atas. Jangan cuma mengandalkan warna (WCAG 1.4.1): pada deuteranopia hijau dan merah sama-sama jadi cokelat zaitun, jadi tambahkan teks atau ikon untuk pengguna buta warna. APCA masih draf, bukan standar.