Rebranding kelihatannya sepele: klien minta warna aksen mereka diganti, jadi saya buka file token, ubah satu baris, lalu refresh. Tombol berubah, link berubah, badge berubah. Tapi beberapa tempat tetap memakai warna lama dengan keras kepala — sebuah ikon di header, garis di bawah judul, satu chart kecil. Tidak ada error di console, tidak ada warning, tidak ada yang merah. Hanya warna yang salah, yang seharusnya sudah mati tapi masih hidup.
Pemicunya cuma satu baris ini:
:root {
/* dulu #C3F73A, sekarang warna brand baru */
--brand: #C3F73A;
}Saya kira mengganti satu nilai itu cukup. Ternyata tidak. Yang berubah hanya elemen yang benar-benar membaca var(--brand). Sisanya — yang menyimpan warna sebagai literal — tetap diam di warnanya sendiri.
Kenapa ini terjadi
CSS custom property itu bukan "find and replace" global. Dia cuma sebuah variabel. Hanya kode yang secara eksplisit menulis var(--brand) yang ikut berubah ketika nilainya saya ganti. Apapun yang menulis warna sebagai literal — entah #C3F73A, entah rgb(195, 247, 58) — sama sekali tidak tahu bahwa ada token bernama --brand. Mereka tidak terhubung. Mengganti --brand tidak menyentuh mereka sedikit pun.
Dan literal warna lama itu ternyata bersembunyi di lebih banyak tempat daripada yang saya kira:
- CSS yang sudah dikompilasi. Beberapa baris di file hasil build memuat hex secara langsung, bukan
var(--brand). Entah karena warnanya ditulis hardcoded di komponen, entah karena ada langkah build yang meng-inline-kan nilainya. - Bundle JS. Ada string warna inline di dalam bundle — dipakai untuk men-set style lewat JavaScript, mewarnai canvas, atau mengisi konfigurasi chart. Itu cuma string biasa di mata bundler, bukan referensi ke token.
- Atribut SVG.
fill="..."danstroke="..."pada ikon dan ilustrasi menyimpan warna langsung di markup. Ini penyebab ikon header saya keras kepala. - Fallback inline-style. Beberapa elemen punya
style="..."inline sebagai fallback, dengan warna lama tertanam di situ.
Tapi alasan sebenarnya kenapa saya kebobolan bukan cuma soal banyaknya tempat. Masalahnya cara audit saya salah. Saya nge-grep proyek hanya untuk string hex-nya:
# yang saya lakukan — dan ini tidak cukup
grep -rin "#C3F73A" src/Pencarian itu melewatkan dua hal sekaligus. Pertama, banyak warna tidak ditulis sebagai hex — ditulis sebagai triplet RGB desimal, 195, 247, 58, yang tidak akan pernah cocok dengan pola #C3F73A. Kedua, saya membatasi pencarian ke src/ dan secara naluri mengecualikan file *compiled* dan *.min karena "itu kan cuma hasil build". Padahal justru di situ literal-nya bersembunyi.
Satu hal lagi yang sempat membuat saya buang waktu: tidak semua "warna salah" itu benar-benar warna yang salah. Ada satu elemen yang hex-nya sudah betul sama persis dengan yang lain, tapi tetap terlihat berbeda — lebih terang, lebih menyala. Itu bukan bug kode. Itu efek Helmholtz-Kohlrausch: warna yang sama bisa terlihat berbeda tergantung konteks — sebagai teks tipis di atas latar gelap dia tampak lebih menyala dibanding sebagai blok solid besar. Mata yang berbohong, bukan hex yang salah. Tidak ada perubahan kode yang bisa "memperbaiki" itu, dan saya hampir saja mengoprek nilai yang sebenarnya sudah benar.
Perbaikannya
Pertama, perbaiki cara grep. Cari hex DAN triplet RGB desimal-nya sekaligus, dan jangan kecualikan apapun:
# cari kedua bentuk warna, di SEMUA file termasuk hasil build
grep -rin -e "#C3F73A" -e "195, *247, *58" .Lalu baca CSS hasil kompilasi baris demi baris — jangan disaring. File *.min dan *compiled* justru harus ikut dibaca, bukan dilewati. Setelah itu sisir atribut SVG dan string literal di JS secara terpisah, karena keduanya tidak akan muncul kalau cuma mengandalkan instinct "warna ada di CSS".
Hasilnya, warna lama saya temukan di tiga jenis tempat, dan masing-masing diperbaiki dengan cara yang berbeda:
<!-- SVG: ganti literal di atribut jadi currentColor -->
<svg class="brand-icon">
<!-- sebelum: fill="#C3F73A" -->
<path fill="currentColor" d="..." />
</svg>/* CSS hasil build / inline-style: arahkan ke token, jangan literal */
.brand-icon { color: var(--brand); }
.underline { border-color: var(--brand); }// JS: jangan tanam hex, baca dari token saat runtime
const brand = getComputedStyle(document.documentElement)
.getPropertyValue("--brand")
.trim();
chart.setColor(brand); // bukan chart.setColor("#C3F73A")Untuk SVG, fill="currentColor" membuat ikon mewarisi color dari CSS, dan color-nya sendiri mengarah ke var(--brand). Untuk JS, alih-alih menanam string warna, saya baca nilai token dari :root saat runtime, jadi satu-satunya sumber kebenaran tetap --brand. Setelah ketiganya diarahkan ulang ke token, ganti warna brand sekarang benar-benar cukup dengan mengubah satu baris.
Pelajaran
Mengganti sebuah --token itu hanya menjangkau konsumen var(). Token bukan sapu ajaib yang merepaint seluruh proyek — dia cuma menyentuh kode yang memang sudah bertanya ke dia. Untuk benar-benar merebrand sebuah warna, kamu harus berburu setiap literal hardcoded di seluruh CSS, JS, dan SVG — dan kamu harus mencari berdasarkan hex sekaligus triplet RGB-nya, karena warna yang sama bisa tertulis dalam dua bentuk. Jangan kecualikan file hasil build dari pencarian; di situlah literal paling sering bersembunyi. Dan terakhir, pisahkan perbedaan warna yang sungguhan (hex, alpha, atau gradient stop yang beda) dari yang cuma persepsi (hex sama tapi terlihat beda karena konteks). Yang pertama diperbaiki dengan kode. Yang kedua tidak akan pernah, sekeras apapun kamu mengoprek nilainya.
