Design Token dengan Tailwind: Theme yang Selamat dari Rebrand

Foto oleh Andy Brown

Foto oleh Andy Brown
Seorang klien pernah meminta saya mengganti warna brand mereka. Permintaan sederhana — kecuali warna lama itu di-hardcode di 340 tempat di seluruh codebase: utility class, nilai hex di CSS, inline style, satu-dua SVG, dan beberapa konfigurasi chart. Yang seharusnya edit satu baris menjadi seminggu penuh grep, regresi, dan QA di setiap halaman. Proyek itulah alasan saya sekarang menyiapkan design token sejak hari pertama, bahkan untuk landing page.
Artikel ini adalah arsitektur token yang saya pakai dengan Tailwind — diperbarui untuk v4, di mana directive @theme menjadikan CSS variable mekanisme theming native. Tujuannya satu ujian yang jujur: jika warna brand, skala tipografi, atau ritme spacing berubah, berapa baris yang Anda edit? Jawaban yang benar adalah satu per keputusan.
Lepaskan semua tooling-nya dan design token hanyalah keputusan desain yang diberi nama: nilai 0.75rem tidak berarti apa-apa, tapi radius-card sama dengan 0.75rem adalah pernyataan bahwa card di sistem ini punya pembulatan segini. Nama membawa niat; nilai adalah detail implementasi. Pembalikan itu adalah inti permainannya — kode harus bergantung pada niat, jangan pernah pada nilai mentah.
Mode kegagalan kebanyakan codebase Tailwind adalah melewatkan penamaan dan memakai palet langsung. Class text-orange-500 tidak mengatakan aksen brand; ia mengatakan oranye persis ini, selamanya. Utility palet Tailwind adalah primitif, dan primitif di kode komponen adalah nilai hardcode dengan ergonomi lebih baik. Mereka terasa seperti sistem, dan justru itulah yang membuat mereka berbahaya saat rebrand.
Setiap sistem token yang selamat dari kontak dengan rebrand berakhir dengan tiga tier yang sama, apa pun sebutan timnya:
| Tier | Contoh | Berubah ketika... |
|---|---|---|
| Primitif (mentah) | color-orange-500, unit dasar spacing, type ramp lengkap | Praktis tidak pernah — ini fisika Anda. Komponen tidak boleh mereferensikannya langsung. |
| Semantik (alias) | color-brand, color-surface, text-muted, radius-card | Saat rebrand atau ganti theme. Masing-masing menunjuk ke primitif; mengarahkan ulang satu alias menata ulang semua pemakainya. |
| Komponen | button-primary-bg, input-border-focus | Saat satu komponen menyimpang dari default semantik. Buat dengan malas — kebanyakan komponen tidak pernah membutuhkannya. |
Tailwind v4 memindahkan theming dari konfigurasi JavaScript ke dalam CSS itu sendiri. Variable yang dideklarasikan di dalam directive @theme bukan custom property CSS biasa — masing-masing menginstruksikan Tailwind untuk menghasilkan utility class. Deklarasikan token warna dan Anda otomatis mendapat utility bg, text, border, dan fill untuknya; deklarasikan sebagai alias dari primitif dan seluruh rantainya tetap hidup:
/* app/globals.css — Tailwind v4 token architecture */
@import "tailwindcss";
@theme {
/* TIER 1 — primitives: raw values, never used in components */
--color-orange-500: oklch(0.71 0.19 41);
--color-slate-900: oklch(0.21 0.04 266);
--spacing: 0.25rem; /* one base unit drives the scale */
--font-sans: var(--font-inter);
/* TIER 2 — semantic: the only names components may use */
--color-brand: var(--color-orange-500);
--color-surface: var(--color-slate-900);
--color-text-muted: oklch(0.71 0.03 264);
--radius-card: 0.75rem;
/* TIER 3 — component tokens, only when a component
genuinely needs its own knob */
--color-button-primary-bg: var(--color-brand);
}
/* Components now write: bg-brand, bg-surface, text-text-muted,
rounded-card — and a rebrand edits ONE line:
--color-brand: var(--color-blue-600); */Perhatikan apa yang dihasilkan struktur ini: komponen hanya pernah melihat nama tier dua. Class di JSX terbaca seperti bahasa desain — bg-surface, text-text-muted, rounded-card — dan pemetaan dari bahasa ke piksel hidup di tepat satu file. Saat portfolio ini perlu aksen oranyenya dituning demi kontras di latar gelap, editnya satu nilai oklch. Setiap card, tombol, link, dan focus ring mengikuti.
Sistem token tidak mati karena kekurangan tooling; mereka mati karena nama yang mengkodekan hal yang salah. Empat aturan menjaga nama tetap jujur:
Namai perannya, bukan nilainya
color-danger, bukan color-red-600. Hari ketika danger menjadi oranye — dan di satu proyek ERP itu benar terjadi, demi aksesibilitas — namanya tetap berkata jujur.
Namai penggunaannya, bukan layarnya
surface-raised mengalahkan card-background-on-dashboard. Token yang ter-scope ke satu layar berlipat ganda selamanya; token yang ter-scope ke peran visual akan dipakai ulang.
Jadikan skalanya semantik juga
Token spacing seperti space-section dan space-stack-sm lebih awet daripada langkah mentah di kode komponen, karena perubahan ritme adalah keputusan desain, bukan empat puluh dua edit padding.
Dua tier dalam nama, selalu
Jika class komponen mereferensikan sesuatu dengan nomor palet di dalamnya, batasnya bocor. Lint untuk itu: grep CI yang gagal saat menemukan utility palet di dalam komponen itu kasar tapi sangat efektif.
Kebocoran paling umum bukan di CSS — tapi di JSX hasil copy-paste dari component library dan snippet buatan AI, yang datang penuh slate-800 dan indigo-500. Anggarkan waktu review untuknya: setiap class palet yang Anda merge hari ini adalah satu hasil grep saat rebrand berikutnya.
@theme milik Tailwind sempurna saat web app adalah satu-satunya konsumen. Begitu token harus memberi makan aplikasi React Native, Android native, template email, atau library Figma, Anda butuh sumber kebenaran yang agnostik terhadap tool. Itulah yang diberikan format Design Tokens Community Group: skema JSON di mana setiap token mendeklarasikan nilai bertipe dengan prefiks dolar, dirancang khusus untuk interoperabilitas antara tool desain dan build system. Style Dictionary kemudian mengompilasi JSON itu menjadi apa pun yang dimakan tiap platform:
// tokens/color.tokens.json — DTCG format, the tool-agnostic source
{
"color": {
"brand": {
"$type": "color",
"$value": "#FF6F20",
"$description": "Primary brand accent"
},
"surface": {
"$type": "color",
"$value": "#0F172A"
}
}
}
// style-dictionary.config.js — fan out to every platform
export default {
source: ["tokens/**/*.tokens.json"],
platforms: {
css: { transformGroup: "css", files: [{ destination: "tokens.css", format: "css/variables" }] },
ts: { transformGroup: "js", files: [{ destination: "tokens.ts", format: "javascript/es6" }] },
android: { transformGroup: "android", files: [{ destination: "colors.xml", format: "android/colors" }] }
}
}Untuk proyek Next.js solo, lapisan ini berlebihan — @theme saja sudah jumlah arsitektur yang tepat. Saya meraih setup DTCG-plus-Style-Dictionary saat platform kedua muncul atau saat desainer memelihara token di Figma dengan plugin yang mengekspor format tersebut. Keputusan arsitekturnya sama seperti biasa: satu sumber kebenaran, dikompilasi keluar, tidak pernah dipelihara paralel.
Anda tidak tahu sistem token Anda bekerja sampai Anda mensimulasikan peristiwa yang menjadi alasan keberadaannya. Punya saya menjalani latihan ini sebelum design system dirilis:
Design token bukan kemewahan perusahaan besar — mereka adalah beda antara rebrand yang selesai sore itu juga dan yang memakan satu sprint. Tailwind v4 menurunkan biaya melakukannya dengan benar menjadi nyaris nol: blok @theme terstruktur dengan primitif, alias semantik, dan disiplin menjauhkan nama palet dari komponen. Siapkan sejak hari pertama. Grep 340 kemunculan adalah wujud dari menunggu.
Jalankan audit cepat sekarang: cari di komponen Anda class apa pun yang mengandung nomor palet seperti 500 atau nilai hex mentah. Panjang daftar hasil itu adalah persis seberapa mahal rebrand Anda berikutnya.