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.

StackUnit reuse
React / Vue / SvelteKomponen (<Button>, <Card>)
Laravel BladeBlade component (<x-button>)
Hugo / JekyllPartial ({{ partial "card.html" . }})
HTML statisLoop 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));
}
ToolFungsi
clsxMenggabungkan class kondisional ({ "opacity-50": disabled })
tailwind-mergeMenyelesaikan konflik: cn("p-2", "p-4") → "p-4"
cvaMendeklarasikan 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"]
}
OpsiKegunaan
tailwindStylesheetv4: path ke file CSS utama agar token & utility kustom dikenali
tailwindConfigv3: path ke tailwind.config.js
tailwindFunctionsSortir juga isi argumen fungsi (clsx, cn, cva)
tailwindAttributesAtribut 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:

  1. Pecah elemen. Wrapper tambahan sering lebih jelas daripada satu elemen dengan layout + tipografi + warna sekaligus.
  2. Pakai token, bukan arbitrary value. text-accent-800 lebih bermakna daripada text-[#92400e].
  3. Kelompokkan varian di cva. State kompleks tidak lagi berupa ternary panjang di JSX.
  4. Manfaatkan warisan CSS. Set text-sm text-zinc-600 sekali di parent, bukan di setiap anak.
  5. Gunakan space-y-*/gap-* di parent alih-alih mt-* di tiap anak.
  6. Plugin typography untuk konten. Satu class prose menggantikan styling manual h2, p, ul di 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

  1. Interpolasi nama class (`text-${size}`). Selalu tulis class utuh; pilih dengan map atau cva.
  2. Class dari database/CMS tidak muncul. Tailwind tidak melihat data runtime — pakai @source inline(...) atau batasi ke set class yang tertulis di kode.
  3. Library komponen di node_modules tampil tanpa gaya. Tambahkan @source ke path paketnya.
  4. Folder yang di-ignore Git ternyata berisi template. Pindahkan, atau daftarkan eksplisit dengan @source.
  5. Menggabungkan class tanpa tailwind-merge. "p-2 p-4" hasilnya bergantung urutan di CSS, bukan urutan di string.
  6. Plugin Prettier tidak menyortir. Biasanya karena tailwindStylesheet belum diset (v4) atau plugin tidak di posisi terakhir.
  7. Membuat @apply untuk semua hal demi “HTML bersih”. Ini memindahkan kompleksitas, bukan menghilangkannya.

Ringkasan

  • Reuse lewat komponen/partial; @apply hanya untuk kasus khusus.
  • clsx + tailwind-merge + cva untuk varian yang rapi dan aman konflik.
  • prettier-plugin-tailwindcss + tailwindStylesheet untuk 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.

Comments