Contrast & Accessibility — UI/UX

Kontras Warna dan WCAG Kontras adalah perbedaan luminance antara foreground (teks) dan background. Kontras yang cukup sangat penting untuk readability dan…

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

Menghitung rasio kontras

Rasionya dihitung dari luminans relatif (L): terang sebuah warna, dari 0 (hitam) sampai 1 (putih). Langkahnya, untuk hijau Bu Sri #15803d:

  1. Tiap kanal dibagi 255: R 21 jadi 0,0824, G 128 jadi 0,5020, B 61 jadi 0,2392.
  2. 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.
  3. L = 0,2126 × R + 0,7152 × G + 0,0722 × B = 0,00159 + 0,15438 + 0,00337 = 0,1593.
  4. 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"

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

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 */

📌 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

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.

Yang akan kamu pelajari