Developer menyukai solusi instan. Saat tiket berbunyi "tambahkan dark mode," jalan termudah terlihat jelas: tulis light.css, tulis dark.css, dan beralihlah di antara keduanya. Terasa bersih. Cepat dirilis. Untuk proyek sampingan kecil dengan tiga komponen, ini mungkin masih bisa bertahan. Namun begitu aplikasi Anda tumbuh melampaui segelintir modul, file kedua tersebut berhenti menjadi aset dan malah menjadi beban yang harus Anda kelola secara duplikat.

Jebakan Dua File

Logikanya tampak masuk akal pada pandangan pertama. Pemisahan tanggung jawab (separation of concerns), bukan? Hal-hal terang di sini, hal-hal gelap di sana. Anda membuka dua buffer di editor Anda. Anda menyalin gaya kartu dari file terang ke file gelap, mengganti #ffffff dengan #1a1a1a, dan selesai.

Masalahnya bukan pada minggu pertama. Masalahnya muncul pada bulan keenam, ketika seorang desainer meminta border radius yang sedikit berbeda pada tombol utama, atau ketika tim produk menginginkan status peringatan baru pada formulir pembayaran. Anda memperbarui stylesheet terang. Anda melihat sekilas stylesheet gelap. Mungkin Anda ingat untuk menyalin perubahannya. Mungkin tidak. Celah itulah tempat kualitas mati. Anda tidak lagi memelihara satu antarmuka. Anda memelihara dua antarmuka paralel yang kebetulan berbagi kerangka HTML yang sama.

Pergeseran Tema Tidak Terelakkan

Celah ini memiliki nama yang mulai dikenali oleh tim frontend: theme drift (pergeseran tema). Ini terjadi ketika dua stylesheet Anda berkembang dengan kecepatan yang berbeda. Penyesuaian padding di sini. Perubahan shadow di sana. File gelap menjadi "saudara" yang terabaikan. Atau lebih buruk lagi, menjadi sumber ketakutan. Developer mulai menghindari perubahan karena menyentuh satu tema berarti harus mencari di file lain untuk menduplikasi pekerjaan tersebut.

Beban kognitif meningkat dengan cepat. Anda ingin menulis CSS sekali. Sebaliknya, Anda menulisnya dua kali, dan sekarang Anda membayar "bunga" atas utang tersebut setiap kali sistem desain berubah. Ikon tidak sejajar dalam mode gelap karena seseorang memperbarui flex gap di file terang dan lupa menyamakannya. Focus ring menghilang karena aturan aksesibilitas baru hanya masuk ke dalam satu lembar saja. UI tidak hanya terlihat salah. Ia mulai terasa rusak.

Gunakan Token Semantik

Solusinya bukanlah alat diff yang lebih baik atau peninjauan kode yang lebih ketat. Solusinya adalah cara berpikir yang berbeda tentang warna. Berhentilah mengatur gaya berdasarkan tampilan literal dan mulailah mengaturnya berdasarkan tujuan. Di sinilah token semantik berperan.

Alih-alih menetapkan latar belakang putih pada sebuah kartu, tetapkan latar belakang surface. Alih-alih memilih antara hitam dan off-white untuk teks, pilihlah warna teks. Komponen tersebut tidak tahu atau tidak peduli apakah pengguna lebih menyukai mode terang atau gelap. Ia hanya meminta token yang sesuai dengan tugasnya.

Pikirkan tentang tombol standar. Dalam dunia dua file, .btn berada di stylesheet terang dengan latar belakang putih dan border gelap. Kembarannya berada di stylesheet gelap dengan latar belakang hampir hitam dan border yang lebih terang. Itu berarti dua kali lipat kode untuk satu tombol. Dengan token, .btn hanya memiliki satu deklarasi: latar belakangnya adalah var(--color-surface-secondary) dan border-nya adalah var(--color-border-default). Nilai-nilainya sendiri berada di tingkat root. Saat situs berada dalam mode terang, --color-surface-secondary akan menghasilkan sesuatu seperti #f8f9fa. Dalam mode gelap, token yang sama akan menghasilkan #2d2d2d. Komponen tombol tidak pernah berubah. Hanya data di bawahnya yang berubah.

Perbedaan antara struktur dan data ini halus namun kuat. Komponen kartu Anda mendefinisikan tata letak, jarak (spacing), tipografi, dan elevasi satu kali saja. Lapisan tema Anda mendefinisikan paletnya. Pemisahan itulah yang memang ditujukan untuk CSS custom properties.

Bagaimana Arsitekturnya Berubah

Pendekatan ini secara fundamental merestrukturisasi cara Anda menulis gaya.

Cara lama biasanya terlihat seperti ini:

  • Sebuah stylesheet kartu terang yang mendefinisikan padding, radius, latar belakang, warna teks, dan shadow.
  • Sebuah stylesheet kartu gelap yang mendefinisikan ulang sebagian besar properti yang sama hanya untuk membalikkan warna.
  • Sebuah lapisan logika yang memutuskan stylesheet mana yang akan dimuat atau kelas mana yang akan diaktifkan pada body.

Cara baru terlihat seperti ini:

  • Satu stylesheet kartu yang mendefinisikan tata letak dan menetapkan token semantik.
  • Satu file tema yang mendefinisikan apa arti token-token tersebut dalam konteks terang.
  • Satu file tema, atau sekadar blok dalam file yang sama, yang mendefinisikan apa arti token-token tersebut dalam konteks gelap.
  • Satu pertukaran atribut tunggal yang mengubah lapisan nilai tanpa menyentuh lapisan komponen.

You keep the setup stable. You only change the data. When the designer wants to introduce a third theme, maybe a high-contrast mode or a midnight blue variant, you do not rewrite the card. You add one more assignment to the token map. The component stays dumb and happy. It still wants a surface color. The theme tells it which surface color to use.

The Data Attribute Switch

Implementation can stay simple and readable. Apply a data attribute to your HTML tag, something like data-theme="dark", and let your token definitions scope under it.

Set your defaults on :root for the light experience so the page renders correctly before JavaScript runs. Then override the token values under [data-theme="dark"]. A tiny script watches for a toggle click, updates the attribute, and every component on the page responds instantly. No class thrashing on individual elements. No importing an entirely separate stylesheet mid-render. The browser already has the variables in memory; it just repaints with new values.

This keeps your code clean in a very practical sense. You do not have to grep across two directories to find every instance of .card. You do not have to worry about specificity wars between competing theme classes stacked on the same node. Your HTML stays readable. Your CSS stays centralized and searchable.

It Is About Values, Not Versions

Dark mode is about values. It is not a second version of your UI. The corners of your card do not get rounder at night. Your grid does not collapse into a different shape. Your type scale does not need a new rhythm. Only the colors shift, and sometimes the shadows breathe a little deeper. Treating darkness as a full reskin is over-engineering that creates maintenance nightmares.

The teams that get this right treat their design system like a database. Components query for properties by name. Themes provide the records. Switching from light to dark is a query parameter change, not a schema rewrite.

That mindset is what saves you from theme drift. One card. One button. One source of truth for spacing and sizing. The palette lives in one place, logically mapped, ready for whatever environment the user prefers.

The Real Takeaway

If you are maintaining two CSS files for light and dark, you are not theming. You are cloning. Move to semantic tokens, scope them with a root-level data attribute, and let your components ask for roles instead of hardcoding appearances. The initial refactor takes effort, but the alternative is an endless game of whack-a-mole across parallel stylesheets. Life is too short to write the same card twice.