September 28, 2026
Pola & Praktik Tailwind CSS.
Menjaga kode Tailwind tetap rapi di proyek nyata: ekstraksi komponen, varian dengan clsx/cva/tailwind-merge, urutan class otomatis dengan prettier-plugin-tailwindcss, menghindari class soup, dan jebakan deteksi source di Tailwind v4.
Setelah memahami dasar Tailwind v4
dan kustomisasi @theme
, tantangan berikutnya adalah skala: bagaimana markup tetap terbaca ketika proyek punya ratusan komponen, dan kenapa class tertentu “tiba-tiba hilang” di build produksi. Catatan ini merangkum pola yang terbukti bekerja.
1. Abstraksi Lewat Komponen, Bukan CSS
Aturan utama Tailwind: jika sebuah kombinasi class dipakai berulang, jadikan komponen di layer template, bukan class CSS baru.
| Stack | Unit reuse |
|---|---|
| React / Vue / Svelte | Komponen (<Button>, <Card>) |
| Laravel Blade | Blade component (<x-button>) |
| Hugo / Jekyll | Partial ({{ partial "card.html" . }}) |
| HTML statis | Loop di template engine, atau @layer components sebagai upaya terakhir |
Contoh React:
type CardProps = { judul: string; children: React.ReactNode };
export function Card({ judul, children }: CardProps) {
return (
<article className="rounded-lg border border-zinc-200 bg-white p-4 shadow-sm dark:border-zinc-700 dark:bg-zinc-900">
<h3 className="mb-2 font-semibold text-zinc-900 dark:text-zinc-100">{judul}</h3>
<div className="text-sm text-zinc-600 dark:text-zinc-400">{children}</div>
</article>
);
}
Class panjang itu kini hanya ditulis sekali. Pemakai cukup menulis <Card judul="...">. Struktur folder komponen seperti ini dibahas di Arsitektur Aplikasi React
.
Kapan memakai @apply/@layer components? Saat markup tidak dikendalikan kita (konten Markdown, widget pihak ketiga) atau untuk elemen yang sangat kecil dan tersebar di banyak template berbeda.
2. Varian Komponen: clsx, tailwind-merge, cva
Komponen biasanya punya varian (ukuran, warna, state). Jangan merangkai string manual — gunakan tool kecil:
npm install clsx tailwind-merge class-variance-authority
// lib/cn.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
| Tool | Fungsi |
|---|---|
clsx | Menggabungkan class kondisional ({ "opacity-50": disabled }) |
tailwind-merge | Menyelesaikan konflik: cn("p-2", "p-4") → "p-4" |
cva | Mendeklarasikan varian secara terstruktur |
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/cn";
const button = cva(
"inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-2 disabled:opacity-50",
{
variants: {
intent: {
primary: "bg-amber-700 text-white hover:bg-amber-800",
ghost: "bg-transparent text-amber-700 hover:bg-amber-50",
},
size: {
sm: "h-8 px-3 text-sm",
md: "h-10 px-4",
},
},
defaultVariants: { intent: "primary", size: "md" },
}
);
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
VariantProps<typeof button>;
export function Button({ intent, size, className, ...props }: ButtonProps) {
return <button className={cn(button({ intent, size }), className)} {...props} />;
}
Perhatikan: semua class ditulis utuh di dalam objek — ini penting untuk deteksi source (bagian 5). className dari pemakai digabung lewat cn, sehingga <Button className="w-full"> bekerja tanpa konflik.
3. Urutan Class Otomatis: prettier-plugin-tailwindcss
Tanpa urutan konsisten, dua developer menulis class yang sama dalam urutan berbeda dan diff jadi berisik. Plugin resmi Prettier menyortir class sesuai urutan yang dipakai Tailwind di CSS output.
npm install -D prettier prettier-plugin-tailwindcss
.prettierrc:
{
"plugins": ["prettier-plugin-tailwindcss"],
"tailwindStylesheet": "./src/app.css",
"tailwindFunctions": ["clsx", "cn", "cva"]
}
| Opsi | Kegunaan |
|---|---|
tailwindStylesheet | v4: path ke file CSS utama agar token & utility kustom dikenali |
tailwindConfig | v3: path ke tailwind.config.js |
tailwindFunctions | Sortir juga isi argumen fungsi (clsx, cn, cva) |
tailwindAttributes | Atribut selain class/className (mis. :class, ngClass) |
Jika memakai plugin Prettier lain (mis. prettier-plugin-go-template untuk template Hugo seperti di situs ini), prettier-plugin-tailwindcss harus di urutan terakhir di array plugins.
Sebelum → sesudah:
<!-- sebelum -->
<div class="text-white p-4 hover:bg-amber-800 flex bg-amber-700 md:p-6 rounded">
<!-- sesudah prettier -->
<div class="flex rounded bg-amber-700 p-4 text-white hover:bg-amber-800 md:p-6">
Aktifkan format on save di editor (lihat Konfigurasi VSCode ) dan pasang extension Tailwind CSS IntelliSense untuk autocomplete serta preview CSS saat hover.
4. Menghindari Class Soup
“Class soup” = deretan 30+ class yang sulit dipindai. Beberapa teknik:
- Pecah elemen. Wrapper tambahan sering lebih jelas daripada satu elemen dengan layout + tipografi + warna sekaligus.
- Pakai token, bukan arbitrary value.
text-accent-800lebih bermakna daripadatext-[#92400e]. - Kelompokkan varian di
cva. State kompleks tidak lagi berupa ternary panjang di JSX. - Manfaatkan warisan CSS. Set
text-sm text-zinc-600sekali di parent, bukan di setiap anak. - Gunakan
space-y-*/gap-*di parent alih-alihmt-*di tiap anak. - Plugin typography untuk konten. Satu class
prosemenggantikan styling manualh2,p,uldi artikel Markdown.
<!-- ❌ soup -->
<li class="mt-2 text-sm text-zinc-600">...</li>
<li class="mt-2 text-sm text-zinc-600">...</li>
<!-- ✅ -->
<ul class="space-y-2 text-sm text-zinc-600">
<li>...</li>
<li>...</li>
</ul>
5. Jebakan Deteksi Source di v4
Tailwind tidak mengeksekusi kode Anda. Ia memindai file sebagai teks biasa, mencari token yang terlihat seperti class, lalu hanya men-generate CSS untuk token yang ditemukan.
File yang diabaikan otomatis
- File yang tercantum di
.gitignore - Folder
node_modules - File biner (gambar, video, zip), file CSS, dan lock file package manager
Class dinamis
// ❌ tidak akan pernah di-generate
<div className={`bg-${warna}-600`} />
// ✅ tulis class lengkap, pilih lewat map
const warnaBg = {
merah: "bg-red-600",
hijau: "bg-green-600",
} as const;
<div className={warnaBg[warna]} />
Mengatur source secara eksplisit
@import "tailwindcss";
/* pindai library UI di node_modules */
@source "../node_modules/@acme/ui";
/* abaikan folder besar yang tidak memakai Tailwind */
@source not "../src/legacy";
/* safelist: paksa generate class yang datang dari CMS/database */
@source inline("{hover:,}bg-red-{100..900..100}");
Atau matikan deteksi otomatis sepenuhnya dan daftarkan manual:
@import "tailwindcss" source(none);
@source "../templates";
Studi kasus: situs ini (Hugo)
Situs ini menulis @source "hugo_stats.json"; di main.css. Hugo, dengan opsi buildStats, menulis semua class yang muncul di HTML hasil render ke hugo_stats.json, dan Tailwind memindai file itu. Konsekuensinya: class baru di template baru muncul setelah Hugo me-regenerate hugo_stats.json. Jika class hilang, urutan perbaikannya adalah npx hugo --gc dulu, baru build CSS.
Folder public/ ada di .gitignore, sehingga tidak ikut dipindai — hal yang membingungkan jika tidak tahu aturan .gitignore di atas.
Kesalahan Umum
- Interpolasi nama class (
`text-${size}`). Selalu tulis class utuh; pilih dengan map ataucva. - Class dari database/CMS tidak muncul. Tailwind tidak melihat data runtime — pakai
@source inline(...)atau batasi ke set class yang tertulis di kode. - Library komponen di
node_modulestampil tanpa gaya. Tambahkan@sourceke path paketnya. - Folder yang di-ignore Git ternyata berisi template. Pindahkan, atau daftarkan eksplisit dengan
@source. - Menggabungkan class tanpa
tailwind-merge."p-2 p-4"hasilnya bergantung urutan di CSS, bukan urutan di string. - Plugin Prettier tidak menyortir. Biasanya karena
tailwindStylesheetbelum diset (v4) atau plugin tidak di posisi terakhir. - Membuat
@applyuntuk semua hal demi “HTML bersih”. Ini memindahkan kompleksitas, bukan menghilangkannya.
Ringkasan
- Reuse lewat komponen/partial;
@applyhanya untuk kasus khusus. clsx+tailwind-merge+cvauntuk varian yang rapi dan aman konflik.prettier-plugin-tailwindcss+tailwindStylesheetuntuk urutan class konsisten.- Tailwind memindai teks: tulis class utuh, dan kenali file yang diabaikan (
.gitignore,node_modules). - Gunakan
@source,@source not, dan@source inline()untuk mengontrol deteksi.

Hey! I’m Fanny, the software engineer tending to this digital garden. You can read more about me, or subscribe by email.