Tips Tailwind CSS dengan Claude Code: Panduan UI yang Stabil
Praktik Tailwind CSS dengan Claude Code: design token, responsive UI, dark mode, komponen, safelist, dan visual checks.
Tailwind CSS memudahkan kita membangun UI dengan utility class kecil seperti p-4, grid, text-sm, dan rounded-lg. Claude Code mempercepat proses itu karena bisa membaca codebase, mengedit file, menjalankan command, dan membantu review. Namun jika instruksinya hanya “buat tampilannya lebih bagus”, hasilnya sering menjadi className panjang, warna tidak konsisten, layout mobile rusak, dark mode kurang kontras, atau class dinamis hilang dari CSS produksi.
Artikel ini menjelaskan workflow praktis untuk memakai Claude Code bersama Tailwind CSS. Kita akan membahas design token, responsive utilities, ekstraksi komponen, cara menghindari class soup, dark mode, forms/buttons/cards, safelist dan content scanning, serta visual checks dengan Playwright. Contoh memakai React + TypeScript, tetapi pola review-nya tetap berguna untuk Astro, Next.js, Remix, Vite, atau stack lain.
Referensi resmi yang dipakai adalah Tailwind Theme variables, Responsive design, Dark mode, Detecting classes in source files, dan Adding custom styles. Untuk React dengan TypeScript, lihat React TypeScript guide. Untuk Claude Code, gunakan Claude Code docs. Untuk screenshot comparison, lihat Playwright Visual comparisons.
Kalau prompt Anda masih terlalu umum, baca dulu tips prompt yang lebih baik. Jika targetnya pengalaman mobile penuh, hubungkan juga dengan panduan PWA.
Mulai dari Audit, Bukan Rewrite
Jangan langsung meminta Claude Code mengubah file. Tailwind UI biasanya rusak karena keputusan kecil yang tersebar: warna, spacing, breakpoint, dark mode, CTA, iklan, dan code block. Minta Claude Code membuat laporan dulu.
Periksa penggunaan Tailwind CSS di repository ini. Jangan edit file dulu.
Buat laporan untuk:
- Design token yang sudah ada untuk warna, spacing, radius, shadow, typography
- React component dengan className terlalu panjang
- Layout yang mungkin rusak pada 375px, 768px, dan 1440px
- Bagian yang sudah/belum mendukung dark mode
- Style berulang pada form, button, card, dan badge
- Dynamic Tailwind class yang berisiko tidak masuk CSS produksi
- Visual check yang sudah ada: Playwright, Storybook, atau manual browser check
Di ClaudeCodeLab, Masa pernah memperbaiki spacing card dari desktop saja. Hasilnya, CTA di akhir artikel dan slot iklan terlalu rapat pada mobile. Tailwind class memang kecil, tetapi dampaknya bisa sampai ke conversion path.
Satukan Design Token
Design token adalah nama untuk nilai dasar desain: warna, jarak, font, radius, shadow, dan breakpoint. Tailwind CSS v4 memakai pendekatan CSS-first lewat @theme, sehingga token seperti --color-brand-600 bisa dipakai sebagai utility bg-brand-600. Pada proyek lama yang masih memakai tailwind.config.ts, prinsipnya sama: simpan nilai di theme.extend.
/* src/styles/app.css */
@import "tailwindcss";
@custom-variant dark (&:where(.dark, .dark *));
@theme {
--font-sans: Inter, system-ui, sans-serif;
--color-brand-50: #eef6ff;
--color-brand-100: #d9ebff;
--color-brand-600: #2563eb;
--color-brand-700: #1d4ed8;
--color-ink: #111827;
--color-muted: #6b7280;
--color-surface: #ffffff;
--color-danger: #dc2626;
--radius-card: 0.75rem;
--shadow-card: 0 16px 40px rgb(15 23 42 / 0.08);
}
Instruksi yang baik untuk Claude Code: “Gunakan token brand, surface, dan danger yang sudah ada. Tambahkan @theme hanya jika nilai baru akan dipakai ulang.” Ini lebih aman daripada “pilih warna biru yang lebih bagus,” karena nanti bisa muncul campuran blue, sky, dan indigo.
Responsive Utility Harus Mobile-First
Tailwind bersifat mobile-first. Class dasar berlaku untuk layar kecil, lalu sm:, md:, dan lg: menambahkan perubahan untuk layar lebih lebar. Kesalahan umum adalah membuat desktop grid dulu, lalu menambal mobile setelah rusak.
Perbaiki product grid dengan Tailwind CSS.
Syarat:
- 375px: 1 kolom, 640px+: 2 kolom, 1024px+: 3 kolom
- Gambar selalu square
- Tinggi card konsisten
- CTA button berada di bawah card
- Jangan ubah type Product props
- Setelah edit, cek screenshot mobile dan desktop
type Product = {
id: string;
name: string;
price: number;
imageUrl: string;
};
export function ProductGrid({ products }: { products: Product[] }) {
return (
<section className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
{products.map((product) => (
<article
key={product.id}
className="flex h-full flex-col overflow-hidden rounded-card border border-slate-200 bg-surface shadow-card dark:border-slate-800 dark:bg-slate-950"
>
<img
src={product.imageUrl}
alt={product.name}
className="aspect-square w-full object-cover"
/>
<div className="flex flex-1 flex-col p-4">
<h3 className="line-clamp-2 text-base font-semibold text-ink dark:text-white">
{product.name}
</h3>
<p className="mt-2 text-sm text-muted dark:text-slate-400">
Rp{product.price.toLocaleString("id-ID")}
</p>
<button className="mt-auto rounded-lg bg-brand-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-brand-700 focus:outline-none focus:ring-2 focus:ring-brand-600 focus:ring-offset-2 dark:focus:ring-offset-slate-950">
Lihat detail
</button>
</div>
</article>
))}
</section>
);
}
aspect-square menjaga rasio gambar, flex h-full flex-col membuat card stabil, dan mt-auto menaruh CTA di bawah meskipun judul produk berbeda panjang.
Kurangi Class Soup dengan Komponen
Class soup adalah className yang terlalu panjang sampai sulit dibaca. Tailwind tidak mewajibkan semua style dipindah ke CSS. Biasanya cukup extract komponen yang berulang, seperti button, card, badge, atau input.
import type { ButtonHTMLAttributes, ReactNode } from "react";
type ButtonVariant = "primary" | "secondary" | "danger";
const buttonVariants: Record<ButtonVariant, string> = {
primary: "bg-brand-600 text-white hover:bg-brand-700 focus:ring-brand-600",
secondary:
"border border-slate-300 bg-white text-slate-900 hover:bg-slate-50 focus:ring-slate-400 dark:border-slate-700 dark:bg-slate-900 dark:text-white",
danger: "bg-danger text-white hover:bg-red-700 focus:ring-danger",
};
function cn(...classes: Array<string | false | null | undefined>) {
return classes.filter(Boolean).join(" ");
}
type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
variant?: ButtonVariant;
loading?: boolean;
children: ReactNode;
};
export function Button({
variant = "primary",
loading = false,
disabled,
className,
children,
...props
}: ButtonProps) {
return (
<button
className={cn(
"inline-flex min-h-10 items-center justify-center rounded-lg px-4 py-2 text-sm font-semibold transition focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-60 dark:focus:ring-offset-slate-950",
buttonVariants[variant],
className,
)}
disabled={disabled || loading}
{...props}
>
{loading ? "Memproses..." : children}
</button>
);
}
Minta Claude Code menjaga class sebagai string lengkap. Hindari pola bg-${color}-600 karena Tailwind scanner bisa tidak melihat class tersebut.
Dark Mode, Form, dan CTA Dicek Bersama
Dark mode bukan hanya mengganti background. Teks, border, shadow, input, error, disabled, dan focus ring harus ikut dicek. Untuk form, jangan hanya cek kondisi sukses, karena error dan disabled state sering muncul pada user sungguhan.
"use client";
import type { FormEvent } from "react";
type LeadFormProps = {
onSubmit: (values: { email: string; message: string }) => void;
error?: string;
};
export function LeadForm({ onSubmit, error }: LeadFormProps) {
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
const formData = new FormData(event.currentTarget);
onSubmit({
email: String(formData.get("email") ?? ""),
message: String(formData.get("message") ?? ""),
});
}
return (
<form
onSubmit={handleSubmit}
className="space-y-4 rounded-card border border-slate-200 bg-white p-5 shadow-card dark:border-slate-800 dark:bg-slate-950"
>
<label className="block text-sm font-medium text-slate-900 dark:text-white">
Email
<input
name="email"
type="email"
required
aria-describedby={error ? "lead-form-error" : undefined}
className="mt-1 w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-sm text-slate-900 outline-none focus:border-brand-600 focus:ring-2 focus:ring-brand-600/20 dark:border-slate-700 dark:bg-slate-900 dark:text-white"
placeholder="you@example.com"
/>
</label>
{error ? (
<p id="lead-form-error" className="text-sm font-medium text-danger">
{error}
</p>
) : null}
<button className="w-full rounded-lg bg-brand-600 px-4 py-2.5 text-sm font-semibold text-white hover:bg-brand-700 focus:outline-none focus:ring-2 focus:ring-brand-600 focus:ring-offset-2 dark:focus:ring-offset-slate-950">
Minta konsultasi
</button>
</form>
);
}
Untuk review aksesibilitas, sambungkan juga dengan panduan aksesibilitas Claude Code.
Safelist dan Content Scanning
Tailwind menghasilkan CSS dari class yang terdeteksi di source files. Jika class dibuat dinamis, CSS produksi bisa tidak berisi class tersebut. Gunakan static map.
type Status = "success" | "warning" | "danger";
const statusClasses: Record<Status, string> = {
success: "bg-emerald-50 text-emerald-700 ring-emerald-600/20",
warning: "bg-amber-50 text-amber-800 ring-amber-600/20",
danger: "bg-red-50 text-red-700 ring-red-600/20",
};
export function StatusBadge({ status, label }: { status: Status; label: string }) {
return (
<span
className={`inline-flex items-center rounded-full px-2.5 py-1 text-xs font-semibold ring-1 ring-inset ${statusClasses[status]}`}
>
{label}
</span>
);
}
Jika class berasal dari package UI eksternal atau CMS, pakai @source atau @source inline() secara terbatas.
@import "tailwindcss";
@source "../node_modules/@acme/ui-kit";
@source inline("bg-emerald-50");
@source inline("text-emerald-700");
@source inline("bg-amber-50");
@source inline("text-amber-800");
Visual Check dengan Screenshot
Build sukses tidak menjamin UI benar. Gunakan Playwright untuk viewport penting.
import { expect, test } from "@playwright/test";
const viewports = [
{ name: "mobile", size: { width: 375, height: 812 } },
{ name: "tablet", size: { width: 768, height: 1024 } },
{ name: "desktop", size: { width: 1440, height: 960 } },
];
for (const viewport of viewports) {
test(`pricing page visual check - ${viewport.name}`, async ({ page }) => {
await page.setViewportSize(viewport.size);
await page.goto("/pricing");
await expect(page.getByRole("main")).toHaveScreenshot(
`pricing-${viewport.name}.png`,
{ maxDiffPixelRatio: 0.01 },
);
});
}
Minta Claude Code melaporkan file yang berubah, risiko, viewport yang dicek, kondisi dark mode, dan langkah manual jika Playwright belum tersedia.
Use Case dan Pitfall
| Use case | Tugas untuk Claude Code | Fokus Tailwind |
|---|---|---|
| Landing page | Review hero, CTA, pricing, testimonial bersama | Spacing, heading scale, CTA |
| Dashboard SaaS | Rapikan table, filter, sidebar, empty state | Density, overflow, sticky header |
| Lead form | Cek input, error, success, loading, disabled | Focus ring, label, tap target |
| Content site | Cek artikel, code block, iklan, CTA | Reading width, code scroll, internal link |
Pitfall yang sering muncul: dynamic class, hanya review desktop, dark mode setengah jalan, @apply berlebihan, terlalu banyak warna mirip, form error tidak dicek, dan screenshot dilewati.
Monetisasi dan Catatan Uji
Perbaikan Tailwind sebaiknya membantu revenue path. Situs konten perlu mengecek CTA akhir artikel, produk template perlu mengecek pricing card, dan layanan tim perlu mengecek form konsultasi. ClaudeCodeLab menyediakan free Claude Code cheatsheet, products and templates, dan training or consultation.
Setelah workflow ini dicoba pada halaman ClaudeCodeLab, hasil paling terasa datang dari audit awal dan screenshot 375px. Keduanya menangkap masalah spacing CTA, focus form, dan contrast dark mode sebelum berubah menjadi pekerjaan cleanup className yang panjang.
PDF gratis: cheatsheet Claude Code
Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.
Kami menjaga datamu dan tidak mengirim spam.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Permission safety ladder Claude Code: perluas akses tanpa kehilangan kontrol
Naik dari read-only ke edit terbatas, command bukti, dan cek deploy dengan kontrol yang jelas.
Claude Code Small PR Proof Pack: perubahan kecil yang mudah direview
Paket bukti untuk PR Claude Code: diff, check, URL publik, jalur CTA, dan rollback.
Review gate Claude Code sebelum commit: diff, test, URL publik, dan CTA
Cara memakai Claude Code sebelum commit: diff scope, build, URL publik, link Gumroad, CTA konsultasi, missing test, dan file tidak terkait.