Use Cases (Diperbarui: 2/6/2026)

shadcn/ui dengan Claude Code: panduan praktis React

Gunakan Claude Code dengan shadcn/ui: setup, Button/Card/Form/Dialog/Table, design token, dan cek Playwright.

shadcn/ui dengan Claude Code: panduan praktis React

shadcn/ui bukan library komponen biasa yang hanya di-import dari node_modules. CLI-nya menyalin source code komponen ke project Anda, biasanya ke src/components/ui. Artinya Button, Card, Dialog, dan Table menjadi bagian dari codebase sendiri. Anda bebas mengubahnya, tetapi tim juga harus menjaga konsistensinya.

Model seperti ini cocok dengan Claude Code. Claude Code bisa membaca file yang dihasilkan, memahami class Tailwind, mengubah variant, menyambungkan form, dan menambahkan test Playwright. Namun prompt yang terlalu luas seperti “buatkan dashboard” bisa menghasilkan Button duplikat, konfigurasi Tailwind lama, atau Dialog yang kehilangan struktur aksesibilitas.

Panduan ini memakai contoh Vite + React + TypeScript: install shadcn/ui, tambah Button/Card/Form/Dialog/Table, rapikan design token, cegah copy-paste drift, dan validasi tampilan dengan Playwright. Jika Anda baru mulai, baca dulu panduan awal Claude Code.

Mulai dari dokumentasi resmi

Sebelum mengubah file, beri Claude Code sumber resmi yang harus diikuti. Dokumentasi Vite shadcn/ui menggunakan shadcn@latest, Tailwind CSS v4 menjelaskan theme variables melalui @theme, Radix UI mendokumentasikan perilaku Dialog yang accessible, dan Playwright memakai toHaveScreenshot() untuk visual comparison.

flowchart LR
  A["Claude Code membaca project"] --> B["shadcn/ui init"]
  B --> C["Tambah komponen dasar"]
  C --> D["Pusatkan design token"]
  D --> E["Buat komponen app"]
  E --> F["Cek dengan Playwright"]

Instalasi dan inisialisasi

Contoh ini memakai Vite agar fokus tetap kecil. Prinsipnya bisa dipakai di Next.js, tetapi pemula sebaiknya tidak langsung mencampur UI system, routing, dan Server Components.

pnpm create vite@latest shadcn-claude-demo -- --template react-ts
cd shadcn-claude-demo
pnpm install
pnpm add tailwindcss @tailwindcss/vite
pnpm add -D @types/node

Atur Tailwind plugin dan alias @ di vite.config.ts.

import path from "node:path"
import react from "@vitejs/plugin-react"
import tailwindcss from "@tailwindcss/vite"
import { defineConfig } from "vite"

export default defineConfig({
  plugins: [react(), tailwindcss()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
})

Tambahkan alias yang sama di TypeScript.

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

Minta Claude Code melakukan inspeksi lebih dulu.

Saya ingin menambahkan shadcn/ui ke project Vite + React + TypeScript ini.
Baca dulu package.json, vite.config.ts, tsconfig*.json, dan src/index.css.
Laporkan hanya setup yang kurang. Setelah saya setujui,
jalankan shadcn@latest init dan tambah komponen yang diperlukan.

Lalu jalankan command berikut.

pnpm dlx shadcn@latest init
pnpm dlx shadcn@latest add button card field input label dialog table
pnpm add react-hook-form zod @hookform/resolvers

Dokumentasi form shadcn/ui saat ini menggunakan React Hook Form, Zod, dan komponen Field. Jangan langsung menyalin snippet lama tanpa memastikan cocok dengan versi project.

Mulai dari Button dan Card

Jangan langsung membangun dashboard penuh. Buat komponen kecil yang mudah direview. File hasil shadcn/ui tetap di src/components/ui, sedangkan UI khusus produk masuk ke src/components/app.

import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"

type ProjectSummaryCardProps = {
  name: string
  openIssues: number
  onCreateTask: () => void
}

export function ProjectSummaryCard({
  name,
  openIssues,
  onCreateTask,
}: ProjectSummaryCardProps) {
  return (
    <Card className="max-w-md">
      <CardHeader>
        <CardTitle>{name}</CardTitle>
        <CardDescription>
          Review open UI issues before starting the next task.
        </CardDescription>
      </CardHeader>
      <CardContent>
        <p className="text-3xl font-semibold">{openIssues}</p>
        <p className="text-muted-foreground text-sm">open issues</p>
      </CardContent>
      <CardFooter>
        <Button onClick={onCreateTask}>Add task</Button>
      </CardFooter>
    </Card>
  )
}

Gunakan prompt review yang sempit.

Review hanya src/components/app/ProjectSummaryCard.tsx.
Periksa tipe props, import shadcn/ui, keterbacaan class Tailwind, dan aksesibilitas.
Sarankan perubahan file lain hanya jika wajib, lengkap dengan alasannya.

Form dengan Field, React Hook Form, dan Zod

Form production tidak cukup hanya menampilkan input. Anda perlu validasi, pesan error, aria-invalid, tombol submit disabled saat proses, autocomplete, dan ruang untuk error dari server.

"use client"

import { zodResolver } from "@hookform/resolvers/zod"
import { Controller, useForm } from "react-hook-form"
import * as z from "zod"

import { Button } from "@/components/ui/button"
import {
  Field,
  FieldDescription,
  FieldError,
  FieldGroup,
  FieldLabel,
} from "@/components/ui/field"
import { Input } from "@/components/ui/input"

const contactSchema = z.object({
  email: z.string().email("Enter a valid email address"),
  topic: z.string().min(4, "Use at least 4 characters"),
})

type ContactFormValues = z.infer<typeof contactSchema>

export function ContactForm() {
  const form = useForm<ContactFormValues>({
    resolver: zodResolver(contactSchema),
    defaultValues: {
      email: "",
      topic: "",
    },
  })

  function onSubmit(values: ContactFormValues) {
    console.log("submit", values)
  }

  return (
    <form className="max-w-md space-y-4" onSubmit={form.handleSubmit(onSubmit)}>
      <FieldGroup>
        <Controller
          name="email"
          control={form.control}
          render={({ field, fieldState }) => (
            <Field data-invalid={fieldState.invalid}>
              <FieldLabel htmlFor={field.name}>Email</FieldLabel>
              <Input
                {...field}
                id={field.name}
                type="email"
                aria-invalid={fieldState.invalid}
                autoComplete="email"
              />
              <FieldDescription>We will use this address to reply.</FieldDescription>
              {fieldState.invalid && <FieldError errors={[fieldState.error]} />}
            </Field>
          )}
        />
      </FieldGroup>

      <Button type="submit" disabled={form.formState.isSubmitting}>
        Send
      </Button>
    </form>
  )
}

Untuk form kecil, schema dan komponen boleh berada di satu file. Jika field bertambah, minta Claude Code memisahkan schema.ts, ContactForm.tsx, dan server action secara sengaja.

Dialog dan Table yang aman

Dialog bukan sekadar popup. Radix UI menangani mode modal, focus trap, tombol Escape, dan pengumuman screen reader melalui Title dan Description. shadcn/ui memberi styling, tetapi struktur itu harus tetap ada.

import { useState } from "react"

import { Button } from "@/components/ui/button"
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "@/components/ui/dialog"
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "@/components/ui/table"

type Customer = {
  id: string
  name: string
  plan: "Free" | "Pro" | "Team"
}

const customers: Customer[] = [
  { id: "cus_001", name: "Aoi Tanaka", plan: "Pro" },
  { id: "cus_002", name: "Mika Sato", plan: "Team" },
]

export function CustomerTable() {
  const [selectedCustomer, setSelectedCustomer] = useState<Customer | null>(null)

  return (
    <>
      <Table>
        <TableHeader>
          <TableRow>
            <TableHead>Customer</TableHead>
            <TableHead>Plan</TableHead>
            <TableHead className="text-right">Actions</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          {customers.map((customer) => (
            <TableRow key={customer.id}>
              <TableCell className="font-medium">{customer.name}</TableCell>
              <TableCell>{customer.plan}</TableCell>
              <TableCell className="text-right">
                <Button
                  variant="outline"
                  size="sm"
                  onClick={() => setSelectedCustomer(customer)}
                >
                  Edit
                </Button>
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>

      <Dialog open={selectedCustomer !== null} onOpenChange={() => setSelectedCustomer(null)}>
        <DialogContent>
          <DialogHeader>
            <DialogTitle>Edit customer</DialogTitle>
            <DialogDescription>
              Review the contract plan for {selectedCustomer?.name}.
            </DialogDescription>
          </DialogHeader>
          <DialogFooter>
            <Button variant="outline" onClick={() => setSelectedCustomer(null)}>
              Close
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </>
  )
}

Di aplikasi nyata, biarkan page layer mengambil data lalu kirim customers dan onEdit ke komponen. Dengan begitu Claude Code tidak mengubah data layer saat tugasnya hanya memperbaiki UI.

Design token dan Tailwind

Design token adalah nilai bernama untuk warna, radius, shadow, font, dan spacing. Di Tailwind v4, @theme memengaruhi utility yang dibuat. Komponen shadcn/ui juga memakai CSS variable seperti --background, --primary, dan --border.

@import "tailwindcss";

@theme {
  --font-sans: Inter, system-ui, sans-serif;
  --radius-card: 0.75rem;
  --color-brand-500: oklch(0.62 0.18 250);
}

:root {
  --radius: 0.625rem;
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  --primary: oklch(0.205 0 0);
  --primary-foreground: oklch(0.985 0 0);
  --border: oklch(0.922 0 0);
  --input: oklch(0.922 0 0);
  --ring: oklch(0.708 0 0);
}

.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  --primary: oklch(0.922 0 0);
  --primary-foreground: oklch(0.205 0 0);
  --border: oklch(1 0 0 / 10%);
  --input: oklch(1 0 0 / 15%);
  --ring: oklch(0.556 0 0);
}

Jika project masih memakai Tailwind v3, minta dua rencana terpisah: migrasi ke v4 atau patch minimal yang tetap kompatibel dengan v3. Mencampur keduanya membuat bug styling sulit dilacak.

Cegah copy-paste drift

shadcn/ui memberi source code. Tanpa aturan, tim mudah membuat Button dan Card yang hampir sama di banyak tempat.

AturanAlasan
src/components/ui khusus primitive shadcn/uiPerbedaan mudah dilacak
UI produk masuk src/components/appLogika bisnis tidak bocor ke base layer
Jangan mengubah alias components.json tanpa alasanImport tetap stabil
Beri Claude Code file targetDuplikasi berkurang
File di src/components/ui berasal dari shadcn/ui.
Jangan membuat Button atau Card baru.
Gunakan import yang ada dan letakkan UI produk di src/components/app.
Setelah edit, jalankan rg "function .*Button|export .*Button" src/components
dan laporkan apakah ada duplikasi.
git diff -- src/components/ui
git diff -- src/components/app
pnpm lint
pnpm build

Untuk workflow yang lebih rapi, lihat juga tips produktivitas Claude Code.

Visual check dengan Playwright

Playwright membuat screenshot baseline lalu membandingkan run berikutnya. Sistem operasi, browser, font, dan viewport sebaiknya sama dengan CI.

pnpm create playwright
pnpm playwright install
pnpm dev
pnpm playwright test
import { expect, test } from "@playwright/test"

test("customer table dialog visual state", async ({ page }) => {
  await page.goto("/customers")
  await page.getByRole("button", { name: "Edit" }).first().click()

  await expect(page).toHaveScreenshot("customers-dialog.png", {
    maxDiffPixels: 120,
  })
})

Update snapshot hanya jika perubahan visual memang disengaja.

pnpm playwright test --update-snapshots

Saat gagal, beri Claude Code masalah yang spesifik.

Playwright customers-dialog.png gagal.
Di mobile, tombol footer Dialog terlalu rapat.
Baca hanya src/components/app/CustomerTable.tsx dan CSS terkait.
Perbaiki spacing tanpa menghapus DialogTitle atau DialogDescription.

Tiga use case praktis

Pertama, dashboard admin baru. Button, Card, Table, dan Dialog sudah cukup untuk list, detail, edit, dan confirmation flow. Buat UI statis dulu, baru sambungkan API.

Kedua, standardisasi produk lama. Ganti search form, settings Dialog, atau empty-state Card satu per satu sebelum mencoba redesign besar.

Ketiga, prototype berbayar atau demo klien. Data mock boleh, tetapi batas komponen harus dekat dengan production agar kode bisa dilanjutkan.

Untuk aksesibilitas, gabungkan workflow ini dengan praktik aksesibilitas Claude Code. Periksa label, keyboard behavior, struktur Dialog, dan pesan error.

Kesalahan umum

Kesalahan pertama adalah menempel konfigurasi Tailwind lama ke project v4. Cek versi dan pipeline build sebelum edit.

Kesalahan kedua adalah memasukkan logika bisnis ke components/ui. Variant Button khusus paket berbayar sebaiknya menjadi wrapper di layer app.

Kesalahan ketiga adalah menghapus DialogTitle karena tidak cocok secara visual. Jika tidak ingin terlihat, sembunyikan dengan sengaja tanpa menghapus informasinya.

Kesalahan keempat adalah hanya mengandalkan required. Form production butuh error terjemahan, validasi async, dan error server.

Kesalahan kelima adalah membuat baseline Playwright di environment berbeda dari CI. Font dan viewport saja bisa memicu diff.

Prompt siap pakai dan hasil uji

Goal: tambahkan shadcn/ui ke layar admin Vite + React + TypeScript.
Gunakan Button, Card, Form berbasis Field, Dialog, dan Table.

Constraints:
- Ikuti dokumentasi resmi saat ini
- src/components/ui hanya untuk primitive shadcn/ui
- Komponen khusus app masuk src/components/app
- Form memakai React Hook Form + Zod + Field
- Pertahankan DialogTitle dan DialogDescription
- Jelaskan perubahan Tailwind v4 @theme dan CSS variables
- Tambahkan satu test Playwright toHaveScreenshot

Process:
1. Inspect setup yang ada
2. Usulkan rencana perubahan
3. Implementasi setelah approval
4. Jalankan pnpm build dan pnpm playwright test
5. Ringkas file berubah dan risiko

Hasil uji: di app Vite minimal, workflow ini berhasil mereproduksi instalasi komponen, penambahan warna brand dengan @theme, form berbasis Field, Dialog yang dibuka dari baris Table, dan visual check Playwright. Bagian yang perlu satu putaran lagi hanya render error form; setelah meminta aria-invalid dan FieldError secara eksplisit, diff menjadi jelas.

Untuk tim, simpan prompt ini di CLAUDE.md. ClaudeCodeLab juga menyediakan template berbayar berisi aturan UI, checklist review, dan prompt Claude Code agar tim tidak mengulang penjelasan yang sama di setiap sprint.

#Claude Code #shadcn/ui #React #Tailwind CSS #UI Library
Gratis

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.

Masa

Tentang penulis

Masa

Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.