Programming
Panduan Shadcn/ui di Next.js: Lengkap dan Praktis
Daftar isi
- Masalah yang Diselesaikan shadcn/ui
- Cara Kerja shadcn/ui
- Fitur Utama shadcn/ui
- Kode Komponen Menjadi Milik Proyek
- Komponen yang Bisa Disusun
- Aksesibilitas sebagai Fondasi
- Integrasi dengan Tailwind CSS
- CLI dan Registry
- Perbandingan shadcn/ui dengan Pustaka Komponen Lain
- Pilih shadcn/ui Jika
- Pilih Material UI Jika
- Pilih Chakra UI Jika
- Pilih Ant Design Jika
- Pilih Headless UI Jika
- Latar Belakang Studi Kasus: Dasbor Pesanan untuk Bisnis Lokal
- Tantangan yang Dihadapi
- Pendekatan yang Dipilih
- Getting Started: Prasyarat Sebelum Memasang shadcn/ui
- Perangkat yang Dibutuhkan
- Pengetahuan yang Membantu
- Step 1 — Membuat Proyek Next.js
- Hasil yang Diharapkan
- Mengapa Langkah Ini Penting?
- Kesalahan yang Sering Terjadi
- Step 2 — Menginisialisasi shadcn/ui
- Contoh components.json
- Hasil yang Diharapkan
- Mengapa Langkah Ini Penting?
- Kesalahan yang Sering Terjadi
- Step 3 — Menambahkan Komponen Pertama
- Hasil yang Diharapkan
- Mengapa Langkah Ini Penting?
- Membaca Komposisi Komponen
- Step 4 — Memahami dan Menyesuaikan Token Tema
- Mengapa Nama Semantik Lebih Baik?
- Contoh Tema untuk Bisnis Kuliner
- Kesalahan yang Sering Terjadi
- Step 5 — Membangun Kartu Statistik yang Bisa Dipakai Ulang
- Hasil yang Diharapkan
- Mengapa Langkah Ini Penting?
- Step 6 — Menambahkan Tabel Pesanan
- Hasil yang Diharapkan
- Mengapa Langkah Ini Penting?
- Masalah Responsif pada Tabel
- Step 7 — Menambahkan Dialog untuk Pesanan Baru
- Hasil yang Diharapkan
- Mengapa "use client" Diperlukan?
- Kesalahan yang Sering Terjadi
- Step 8 — Menambahkan Validasi Formulir
- Hasil yang Diharapkan
- Mengapa Validasi Perlu Dipisahkan?
- Menangani Nomor Telepon Indonesia
- Step 9 — Menambahkan Toast untuk Umpan Balik
- Hasil yang Diharapkan
- Mengapa Umpan Balik Harus Spesifik?
- Step 10 — Menghubungkan Formulir dengan Server Action
- Mengapa Validasi Diulang di Server?
- Jangan Mengirim Kesalahan Internal ke Pengguna
- Step 11 — Menambahkan Tema Gelap
- Hasil yang Diharapkan
- Mengatasi Hydration Warning
- Step 12 — Mengatur Struktur Proyek agar Mudah Dipelihara
- Aturan Praktis Penempatan Komponen
- Mengapa Jangan Mengubah Semua Primitive?
- Step 13 — Membuat Variant Tombol Khusus
- Mengapa Variant Lebih Baik daripada Kelas Berulang?
- Step 14 — Menambahkan Status Kosong dan Skeleton
- Mengapa Status Antarmuka Ini Penting?
- Hindari Skeleton yang Terlalu Lama
- Step 15 — Menguji Aksesibilitas dan Navigasi Papan Ketik
- Daftar Uji Papan Ketik
- Periksa Label Formulir
- Periksa Nama Tombol Ikon
- Periksa Kontras dan Fokus
- Step 16 — Menjaga Performa Aplikasi
- Tambahkan Komponen Secukupnya
- Jaga Client Component Tetap Kecil
- Hindari Impor Ikon Berlebihan
- Ukur, Jangan Menebak
- Step 17 — Menangani Pembaruan Komponen
- Kelebihannya
- Kekurangannya
- Strategi Pembaruan yang Aman
- Implementasi Studi Kasus: Hasil yang Dicapai
- Nilai Bisnis yang Terlihat
- Kesalahan Umum Saat Memakai shadcn/ui
- Menganggap shadcn/ui sebagai Paket yang Tidak Boleh Disentuh
- Menaruh Semua Komponen di components/ui
- Memasang Terlalu Banyak Komponen Sekaligus
- Mengubah Warna Tetap di Banyak Tempat
- Mengandalkan Placeholder sebagai Label
- Melupakan Kondisi Gagal
- Mempercayai Semua Registry Pihak Ketiga
- Troubleshooting shadcn/ui
- Modul @/components/ui/button Tidak Ditemukan
- cn Tidak Ditemukan
- Gaya Komponen Tidak Muncul
- Kelas Dinamis Tailwind Tidak Terdeteksi
- Hydration Mismatch
- Dialog Tidak Bisa Dibuka
- Variant TypeScript Ditolak
- Tampilan Berbeda Setelah Menyalin Contoh Lama
- Tips Praktis agar shadcn/ui Tetap Rapi
- Tetapkan Token Sebelum Memoles Halaman
- Buat Daftar Variant yang Disetujui
- Bungkus Pola Bisnis, Bukan Semua Elemen
- Gunakan Git sebagai Pengaman
- Dokumentasikan Keputusan, Bukan Hanya Komponen
- Ulasan Seimbang: Kelebihan dan Kekurangan shadcn/ui
- Kelebihan
- Kekurangan
- Siapa yang Paling Cocok Memakainya?
- Siapa yang Sebaiknya Memilih Pilihan Lain?
- Perbandingan Sumber Komponen dan Template shadcn
- Pelajaran Utama dari Implementasi
- Open Code Bukan Berarti Bebas Tanpa Aturan
- ROI Datang dari Standardisasi
- Primitive dan Domain Harus Dipisahkan
- Aksesibilitas Tetap Perlu Diuji
- Template Bukan Produk Jadi
- Metrik Harus Dibaca Sesuai Konteks
- Langkah Lanjutan Setelah Dasbor Berfungsi
- Kesimpulan
shadcn/ui cocok buat Teman-Teman yang ingin membangun antarmuka React yang rapi, mudah disesuaikan, dan tetap sepenuhnya berada di dalam repositori proyek. Alih-alih memasang pustaka komponen tertutup lalu sibuk menimpa gayanya, shadcn/ui memberikan kode komponen yang bisa dibaca, diubah, dan dikembangkan sesuai kebutuhan produk.
shadcn/ui adalah kumpulan komponen antarmuka yang mudah diakses sekaligus sistem distribusi kode. Komponen ditambahkan langsung ke proyek, jadi tim memiliki kendali penuh atas struktur, gaya, perilaku, dan proses pengembangannya.
Ringkasan singkat: Tutorial ini membahas cara memasang shadcn/ui di Next.js, menambahkan komponen, membangun halaman dasbor, membuat formulir, mengatur tema gelap, menangani kesalahan umum, serta memilih antara shadcn/ui, Material UI, Chakra UI, dan pustaka komponen lain. Contohnya dirancang agar relevan untuk proyek SaaS, dasbor operasional, situs bisnis lokal, dan aplikasi internal di Indonesia.
Masalah yang Diselesaikan shadcn/ui
Membangun antarmuka modern sering terlihat gampang di awal. Kita cukup membuat tombol, kartu, formulir, dialog, dan tabel. Masalahnya baru terasa setelah produk bertambah besar dan setiap halaman mulai memakai variasi desain yang berbeda.
Beberapa masalah yang paling sering muncul antara lain:
-
Tombol di halaman pemasaran tidak konsisten dengan tombol di dasbor.
-
Modal terlihat bagus, tetapi navigasi papan ketiknya bermasalah.
-
Formulir memiliki pesan kesalahan yang berbeda-beda.
-
Tim menumpuk banyak kelas CSS untuk menimpa gaya pustaka.
-
Komponen dari beberapa pustaka memiliki API yang tidak seragam.
-
Perubahan warna merek harus dilakukan di banyak berkas.
-
Hasil buatan alat AI tidak sesuai dengan sistem desain proyek.
-
Pembaruan pustaka memunculkan perubahan tampilan yang tidak diinginkan.
Masalah tersebut bukan cuma soal estetika. Antarmuka yang tidak konsisten dapat memperlambat pengembangan, meningkatkan biaya pengujian, dan membuat pengalaman pengguna terasa kurang matang.
shadcn/ui menawarkan pendekatan berbeda. Teman-Teman mengambil komponen yang diperlukan, memasukkannya ke dalam proyek, lalu memperlakukannya sebagai kode milik tim sendiri.
Pendekatan ini penting karena:
-
Kode mudah diperiksa. Tidak ada lapisan misterius yang sulit ditelusuri.
-
Komponen mudah diubah. Tim dapat menyesuaikan markup, kelas, dan perilaku.
-
Desain lebih konsisten. Komponen memakai token dan pola yang sama.
-
Lebih ramah untuk AI. Kode lokal bisa dibaca dan disunting oleh alat bantu pengembangan.
-
Tidak terikat tampilan bawaan. Produk tidak harus terlihat seperti semua aplikasi lain yang memakai pustaka serupa.
Dokumentasi resmi menyebut shadcn/ui sebagai fondasi untuk membangun sistem desain, bukan sekadar katalog komponen siap pakai. Teman-Teman dapat melihat daftar komponen dan konsep resminya di dokumentasi shadcn/ui.
Cara Kerja shadcn/ui
Pustaka komponen tradisional biasanya dipasang sebagai paket. Kode implementasinya tetap berada di node_modules, sedangkan aplikasi hanya mengimpor komponen tersebut.
Contohnya kira-kira seperti ini:
import { Button } from "some-ui-library"
Teman-Teman dapat memakai komponennya, tetapi perubahan mendalam sering memerlukan properti khusus, penimpaan CSS, atau pembungkus tambahan.
Pada shadcn/ui, CLI menambahkan berkas komponen ke proyek. Setelah memasang tombol, misalnya, proyek akan memiliki berkas seperti:
components/
└── ui/
└── button.tsx
Aplikasi tetap mengimpornya dengan cara yang familiar:
import { Button } from "@/components/ui/button"
Perbedaannya terletak pada kepemilikan kode. Berkas button.tsx berada di repositori Teman-Teman dan bisa disunting langsung.
Anggap shadcn/ui seperti paket rancangan rumah yang sudah memperhitungkan struktur, aksesibilitas, dan tampilan awal. Teman-Teman menerima rancangannya, lalu bebas mengganti warna, bahan, tata ruang, atau menambah ruangan tanpa meminta izin kepada penyedia paket.
Pendekatan ini disebut Open Code. Menurut dokumentasi resminya, shadcn/ui dibangun di atas prinsip kode terbuka, komposisi, distribusi, tampilan awal yang baik, dan kesiapan untuk alur kerja berbantuan AI.
Fitur Utama shadcn/ui
Kode Komponen Menjadi Milik Proyek
Setiap komponen yang ditambahkan berada di dalam proyek. Ini memudahkan peninjauan kode, pelacakan perubahan melalui Git, dan penyesuaian desain.
Tim juga dapat membuat aturan internal. Contohnya, semua tombol utama wajib memiliki tinggi tertentu atau semua dialog wajib mencatat peristiwa analitik.
Komponen yang Bisa Disusun
Komponen shadcn/ui dirancang agar dapat digabungkan. Dialog, dropdown, formulir, tombol, dan kartu memakai pola yang relatif konsisten.
Komposisi membantu tim menghindari komponen raksasa dengan puluhan properti. Struktur halaman tetap jelas karena bagian-bagiannya terlihat langsung di JSX.
Aksesibilitas sebagai Fondasi
Banyak komponen shadcn/ui dibangun di atas primitive antarmuka yang menangani detail seperti:
Baca juga OpenCode Free: Agen AI Coding Gratis dan Open Source
-
Navigasi papan ketik.
-
Fokus saat dialog dibuka.
-
Atribut ARIA.
-
Hubungan label dengan kolom input.
-
Perilaku menu dan popover.
-
Penutupan dialog dengan tombol
Escape.
Meski begitu, aksesibilitas bukan tombol otomatis yang langsung menyelesaikan semuanya. Tim tetap perlu menguji teks alternatif, urutan fokus, kontras warna, ukuran target sentuh, dan kualitas label.
Integrasi dengan Tailwind CSS

shadcn/ui memakai kelas utilitas untuk mengatur tampilan. Pendekatan ini membuat gaya komponen terlihat dekat dengan markup dan mudah disesuaikan.
Teman-Teman tidak wajib menghafal semua utilitas sejak awal. Cukup pahami pola dasarnya:
<div className="rounded-xl border bg-card p-6 text-card-foreground shadow-sm">
Konten kartu
</div>
Kelas tersebut mengatur sudut, garis tepi, warna latar, jarak dalam, warna teks, dan bayangan.
CLI dan Registry
shadcn/ui bukan hanya sekumpulan berkas. Ia juga menyediakan CLI dan format registry untuk mendistribusikan komponen.
CLI membantu Teman-Teman:
-
Menginisialisasi konfigurasi.
-
Menambahkan komponen tertentu.
-
Memasang dependensi yang diperlukan.
-
Mengambil komponen dari registry.
-
Menjaga struktur impor tetap konsisten.
Ekosistem registry juga memungkinkan komunitas menyediakan blok, pola, dan komponen tambahan. Tetap periksa kualitas kode serta lisensinya sebelum memasukkan registry pihak ketiga ke produk.
Perbandingan shadcn/ui dengan Pustaka Komponen Lain
Tidak ada satu pilihan yang selalu paling unggul. Pilihan yang tepat bergantung pada kapasitas tim, target peluncuran, kebutuhan merek, dan seberapa jauh antarmuka akan disesuaikan.
Kriteria | shadcn/ui | Material UI | Chakra UI | Ant Design | Headless UI |
|---|---|---|---|---|---|
Model distribusi | Kode disalin ke proyek | Paket NPM | Paket NPM | Paket NPM | Paket primitive |
Kendali atas kode | Sangat tinggi | Sedang | Sedang | Sedang | Tinggi |
Tampilan awal | Minimal dan modern | Material Design | Bersih dan fleksibel | Cocok untuk aplikasi bisnis | Hampir tanpa gaya |
Penyesuaian mendalam | Mudah, tetapi perlu disiplin | Melalui tema dan penimpaan | Melalui properti dan tema | Melalui token dan konfigurasi | Harus membangun gaya sendiri |
Kecepatan memulai | Cepat | Sangat cepat | Sangat cepat | Sangat cepat | Sedang |
Risiko ketergantungan desain | Rendah | Sedang | Sedang | Sedang | Rendah |
Beban pemeliharaan lokal | Lebih tinggi | Lebih rendah | Lebih rendah | Lebih rendah | Tinggi |
Cocok untuk sistem desain khusus | Sangat cocok | Cocok | Cocok | Cukup cocok | Sangat cocok |
Cocok untuk prototipe singkat | Cocok | Sangat cocok | Sangat cocok | Sangat cocok | Cukup cocok |
Pilih shadcn/ui Jika
-
Produk membutuhkan identitas visual khusus.
-
Tim ingin memiliki kode komponen.
-
Proyek memakai React atau Next.js dan Tailwind CSS.
-
Komponen akan banyak disesuaikan.
-
Tim ingin membangun sistem desain secara bertahap.
-
Alur kerja pengembangan banyak dibantu AI.
-
Transparansi implementasi lebih penting daripada pembaruan otomatis.
Pilih Material UI Jika
-
Produk mengikuti Material Design.
-
Tim membutuhkan katalog komponen yang luas sejak hari pertama.
-
Penyesuaian tingkat rendah bukan prioritas.
-
Kecepatan membangun aplikasi administrasi lebih penting daripada identitas visual unik.
Pilih Chakra UI Jika
-
Tim menyukai API berbasis properti.
-
Proyek perlu produktif dengan konfigurasi yang relatif ringan.
-
Tim tidak ingin menyimpan implementasi setiap komponen secara lokal.
Pilih Ant Design Jika
-
Produk berupa aplikasi perusahaan dengan tabel dan formulir yang kompleks.
-
Tim membutuhkan banyak komponen bisnis siap pakai.
-
Tampilan khas Ant Design masih sesuai dengan kebutuhan produk.
Pilih Headless UI Jika
-
Tim desain dan pengembang sudah matang.
-
Semua gaya ingin dibangun sendiri.
-
Proyek membutuhkan primitive perilaku tanpa desain visual yang kuat.
Rekomendasi praktis: shadcn/ui paling menarik ketika diferensiasi desain, kepemilikan kode, dan fleksibilitas jangka panjang lebih penting daripada kenyamanan pembaruan paket terpusat.
Latar Belakang Studi Kasus: Dasbor Pesanan untuk Bisnis Lokal
Agar tutorial tidak berhenti pada tombol dan kartu kosong, kita akan memakai skenario yang realistis. Bayangkan sebuah tim kecil sedang membuat dasbor pesanan untuk usaha kuliner di Jakarta.
Usaha tersebut menerima pesanan dari beberapa wilayah, misalnya:
-
Kebayoran Baru.
-
Tebet.
-
Kemang.
-
Cilandak.
-
Menteng.
-
Kelapa Gading.
Tim operasional membutuhkan halaman untuk melihat jumlah pesanan, omzet, status pengiriman, dan daftar transaksi terbaru. Tampilan harus tetap nyaman dibuka dari laptop kasir maupun tablet.
Tantangan yang Dihadapi
Versi awal dibuat dengan elemen HTML dan kelas yang ditulis langsung di setiap halaman. Dalam beberapa minggu, muncul pola yang tidak konsisten:
-
Tombol “Simpan” memiliki tiga variasi warna.
-
Kolom input memakai ukuran yang berbeda.
-
Kartu ringkasan tidak memiliki jarak yang seragam.
-
Dialog konfirmasi sulit digunakan dengan papan ketik.
-
Tema gelap tidak berjalan di beberapa komponen.
-
Pengembang menghabiskan waktu mengulang kode yang sama.
Masalah terbesarnya bukan kurangnya komponen. Masalahnya adalah tidak adanya fondasi yang konsisten.
Pendekatan yang Dipilih
Tim memutuskan memakai shadcn/ui dengan strategi berikut:
-
Menentukan token warna dan radius terlebih dahulu.
-
Menambahkan komponen hanya saat dibutuhkan.
-
Membuat komponen domain di luar folder
components/ui. -
Menyimpan primitive shadcn/ui tetap kecil.
-
Menguji alur papan ketik dan tampilan seluler.
-
Mendokumentasikan variasi komponen yang disetujui.
Struktur ini menghindari dua ekstrem: menulis semuanya dari nol atau memasang terlalu banyak komponen tanpa rencana.
Getting Started: Prasyarat Sebelum Memasang shadcn/ui
Tutorial ini memakai Next.js, TypeScript, dan Tailwind CSS. Teman-Teman tidak perlu menguasai seluruh ekosistemnya, tetapi sebaiknya sudah mengenal komponen React dan perintah terminal.
Perangkat yang Dibutuhkan
Pastikan perangkat memiliki:
-
Node.js versi LTS yang masih didukung.
-
Pengelola paket seperti
npm,pnpm,yarn, ataubun. -
Penyunting kode.
-
Terminal.
-
Git untuk pencatatan perubahan.
-
Peramban modern.
Periksa Node.js dan npm:
Baca juga ClinePass: Langganan Model Open Weight untuk Coding
node --version
npm --version
Contoh keluaran:
v22.14.0
10.9.2
Nomor versi Teman-Teman mungkin berbeda. Yang penting, perintah dapat dijalankan tanpa pesan bahwa node atau npm tidak ditemukan.
Jika Node.js belum terpasang, unduh versi LTS dari situs resmi Node.js.
Pengetahuan yang Membantu
Beberapa konsep yang akan sering muncul:
-
Komponen React: fungsi yang mengembalikan antarmuka.
-
Props: data yang dikirim ke komponen.
-
State: data yang dapat berubah selama interaksi.
-
TypeScript: JavaScript dengan pemeriksaan tipe.
-
Tailwind CSS: kumpulan kelas utilitas untuk mengatur tampilan.
-
Server Component: komponen Next.js yang secara bawaan dijalankan di server.
-
Client Component: komponen yang dapat memakai state, efek, dan peristiwa peramban.
Tidak perlu menghafal definisinya. Teman-Teman akan melihat penerapannya di setiap langkah.
Step 1 — Membuat Proyek Next.js
Buka terminal, lalu jalankan:
npx create-next-app@latest warung-dashboard
Saat pertanyaan konfigurasi muncul, pilih kurang lebih seperti ini:
Would you like to use TypeScript? Yes
Would you like to use ESLint? Yes
Would you like to use Tailwind CSS? Yes
Would you like your code inside a `src/` directory? No
Would you like to use App Router? Yes
Would you like to use Turbopack? Yes
Would you like to customize the import alias? No
Masuk ke direktori proyek:
cd warung-dashboard
Jalankan server pengembangan:
npm run dev
Buka alamat berikut:
http://localhost:3000
Hasil yang Diharapkan
Peramban menampilkan halaman bawaan Next.js. Terminal juga menampilkan informasi bahwa server siap menerima permintaan.
Ready in 1.2s
Local: http://localhost:3000
Mengapa Langkah Ini Penting?
shadcn/ui membutuhkan fondasi proyek yang jelas. Next.js menyiapkan sistem pembangunan, routing, TypeScript, dan Tailwind CSS agar kita tidak perlu merangkainya satu per satu.
Memilih TypeScript juga membantu saat komponen mulai menerima banyak properti. Kesalahan tipe dapat diketahui lebih cepat sebelum aplikasi dijalankan pengguna.
Kesalahan yang Sering Terjadi
Perintah npx Tidak Ditemukan
Penyebab paling umum adalah Node.js belum terpasang atau terminal belum membaca ulang variabel lingkungan.
Coba:
-
Tutup terminal.
-
Buka terminal baru.
-
Jalankan kembali
node --version. -
Pasang ulang Node.js LTS jika masih gagal.
Port 3000 Sudah Digunakan
Next.js biasanya menawarkan port lain secara otomatis. Teman-Teman juga bisa menentukan port:
npm run dev -- --port 3001
Lalu buka:
http://localhost:3001
Folder Proyek Sudah Ada
Gunakan nama lain atau kosongkan folder sebelumnya:
npx create-next-app@latest warung-dashboard-v2
Step 2 — Menginisialisasi shadcn/ui
Pastikan terminal berada di direktori proyek, lalu jalankan:
npx shadcn@latest init
CLI akan memeriksa proyek dan meminta beberapa pilihan konfigurasi. Opsi dapat berubah mengikuti versi, tetapi umumnya mencakup gaya, warna dasar, variabel CSS, dan alias komponen.
Baca juga Kiro Dev: IDE Agentic AWS untuk Spec-Driven Development
Contoh pilihan:
Which color would you like to use as the base color? Neutral
Would you like to use CSS variables for theming? Yes
Setelah selesai, proyek biasanya memperoleh berkas konfigurasi seperti:
components.json
CLI juga dapat memperbarui berkas CSS global dan menambahkan fungsi utilitas.
Contoh components.json
Isi persisnya dapat berbeda, tetapi bentuk umumnya seperti ini:
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}
Hasil yang Diharapkan
Terminal menampilkan bahwa konfigurasi dan dependensi telah berhasil ditambahkan. Struktur proyek dapat berubah menjadi:
warung-dashboard/
├── app/
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
├── lib/
│ └── utils.ts
├── components.json
├── package.json
└── tsconfig.json
Mengapa Langkah Ini Penting?
Berkas components.json memberi tahu CLI tempat menyimpan komponen, lokasi CSS, dan alias impor yang dipakai. Tanpa konfigurasi yang benar, komponen dapat masuk ke folder yang salah atau menghasilkan jalur impor yang tidak cocok.
Penggunaan variabel CSS juga penting untuk tema. Warna seperti background, foreground, dan primary dapat diganti tanpa menyunting setiap komponen.
Kesalahan yang Sering Terjadi
CLI Tidak Mengenali Kerangka Kerja
Pastikan perintah dijalankan di folder yang memiliki package.json.
ls
Di Windows PowerShell:
Get-ChildItem
Teman-Teman harus melihat package.json, folder app, dan berkas konfigurasi Next.js.
Konfigurasi Tailwind Tidak Terdeteksi
Periksa apakah Tailwind memang dipilih saat membuat proyek. Jalankan aplikasi dan pastikan kelas sederhana bekerja:
export default function Home() {
return <main className="p-10 text-3xl font-bold">Tes Tailwind</main>
}
Jika teks tidak berubah, masalahnya berada pada konfigurasi Tailwind, bukan shadcn/ui.
Alias @/ Bermasalah
Periksa tsconfig.json. Konfigurasi umumnya memuat:
{
"compilerOptions": {
"paths": {
"@/*": ["./*"]
}
}
}
Jika proyek memakai folder src, aliasnya mungkin perlu mengarah ke:
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}
Step 3 — Menambahkan Komponen Pertama
Kita mulai dengan tiga komponen yang sering dipakai:
npx shadcn@latest add button card badge
Setelah perintah selesai, folder berikut biasanya muncul:
components/
└── ui/
├── badge.tsx
├── button.tsx
└── card.tsx
Ganti isi app/page.tsx dengan kode berikut:
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
export default function Home() {
return (
<main className="min-h-screen bg-muted/40 p-6 md:p-10">
<div className="mx-auto max-w-3xl">
<Card>
<CardHeader>
<div className="flex items-center justify-between gap-4">
<CardTitle>Pesanan Hari Ini</CardTitle>
<Badge>Aktif</Badge>
</div>
<CardDescription>
Ringkasan pesanan dari seluruh area layanan.
</CardDescription>
</CardHeader>
<CardContent>
<p className="text-4xl font-bold">128</p>
<p className="mt-2 text-sm text-muted-foreground">
Naik 12% dibandingkan kemarin
</p>
</CardContent>
<CardFooter>
<Button>Lihat semua pesanan</Button>
</CardFooter>
</Card>
</div>
</main>
)
}
Hasil yang Diharapkan
Halaman menampilkan sebuah kartu berisi:
-
Judul “Pesanan Hari Ini”.
-
Badge “Aktif”.
-
Angka 128.
-
Informasi kenaikan 12%.
-
Tombol “Lihat semua pesanan”.
Tampilannya memiliki garis tepi, jarak yang rapi, dan warna yang mengikuti token tema.
Mengapa Langkah Ini Penting?
Kita langsung memakai komponen secara nyata agar alur shadcn/ui terlihat jelas. CLI menambahkan kode, lalu halaman mengimpor komponen dari folder lokal.
Baca juga Codex CLI dari OpenAI: Panduan Lengkap Ngoding di Terminal
Perhatikan bahwa Card terdiri dari beberapa bagian. Struktur ini lebih mudah disesuaikan daripada satu komponen kartu dengan banyak properti khusus.
Membaca Komposisi Komponen
Bagian kartu memiliki tanggung jawab masing-masing:
-
Cardmenjadi pembungkus. -
CardHeadermenampung judul dan deskripsi. -
CardContentmenampung data utama. -
CardFootermenampung tindakan. -
CardTitlememberi struktur judul. -
CardDescriptionmenampilkan informasi pendukung.
Kalau desain berubah, Teman-Teman dapat memindahkan bagian-bagian tersebut tanpa menulis ulang seluruh komponen.
Step 4 — Memahami dan Menyesuaikan Token Tema
Buka app/globals.css. Setelah inisialisasi, Teman-Teman akan menemukan variabel warna untuk tema terang dan gelap.
Bentuknya dapat berbeda mengikuti versi, tetapi konsepnya serupa:
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--border: oklch(0.922 0 0);
--radius: 0.625rem;
}
Variabel tersebut dipakai melalui kelas semantik:
<div className="bg-background text-foreground">
<button className="bg-primary text-primary-foreground">
Simpan
</button>
</div>
Mengapa Nama Semantik Lebih Baik?
Hindari menulis warna merek secara berulang seperti:
<button className="bg-blue-600 text-white">
Simpan
</button>
Kode itu terlihat sederhana, tetapi akan merepotkan jika warna merek berubah. Lebih baik gunakan:
<button className="bg-primary text-primary-foreground">
Simpan
</button>
Sekarang perubahan warna cukup dilakukan pada token primary.
Contoh Tema untuk Bisnis Kuliner
Misalnya, merek menggunakan warna hijau hangat. Sesuaikan variabel tema melalui generator resmi atau nilai warna yang sudah diperiksa kontrasnya.
Contoh konseptual:
:root {
--primary: oklch(0.55 0.14 145);
--primary-foreground: oklch(0.98 0.02 145);
--ring: oklch(0.55 0.14 145);
--radius: 0.75rem;
}
Jangan memilih warna hanya karena terlihat bagus di layar sendiri. Periksa kontras teks, terutama pada tombol, badge, pesan kesalahan, dan teks sekunder.
Kesalahan yang Sering Terjadi
Warna Tidak Berubah
Kemungkinan penyebabnya:
-
Variabel yang diubah bukan variabel yang dipakai komponen.
-
Berkas CSS global tidak diimpor.
-
Peramban masih menyimpan hasil lama.
-
Kelas memakai warna tetap seperti
bg-blue-600.
Periksa app/layout.tsx:
import "./globals.css"
Lalu muat ulang halaman secara penuh.
Semua Teks Menjadi Sulit Dibaca
Pastikan pasangan warna tetap serasi:
-
primarydenganprimary-foreground. -
backgrounddenganforeground. -
carddengancard-foreground. -
muteddenganmuted-foreground.
Mengubah latar tanpa menyesuaikan warna teks adalah kesalahan tema yang cukup umum.
Step 5 — Membangun Kartu Statistik yang Bisa Dipakai Ulang
Komponen di components/ui sebaiknya tetap menjadi primitive antarmuka. Komponen yang memahami istilah bisnis, seperti omzet atau pesanan, ditempatkan di luar folder tersebut.
Buat components/stat-card.tsx:
import { ReactNode } from "react"
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
type StatCardProps = {
title: string
value: string
description: string
icon?: ReactNode
}
export function StatCard({
title,
value,
description,
icon,
}: StatCardProps) {
return (
<Card>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
<CardTitle className="text-sm font-medium">
{title}
</CardTitle>
{icon ? (
<span className="text-muted-foreground">{icon}</span>
) : null}
</CardHeader>
<CardContent>
<p className="text-2xl font-bold">{value}</p>
<CardDescription className="mt-1">
{description}
</CardDescription>
</CardContent>
</Card>
)
}
Perbarui app/page.tsx:
Baca juga CodeBuddy: Editor Kode AI untuk Coding Lebih Cepat
import { Package, ShoppingCart, TrendingUp, Wallet } from "lucide-react"
import { StatCard } from "@/components/stat-card"
const stats = [
{
title: "Pesanan",
value: "128",
description: "+12% dibandingkan kemarin",
icon: ShoppingCart,
},
{
title: "Omzet",
value: "Rp8.450.000",
description: "+8,4% dalam 24 jam",
icon: Wallet,
},
{
title: "Siap Dikirim",
value: "24",
description: "Menunggu kurir",
icon: Package,
},
{
title: "Rata-rata Transaksi",
value: "Rp66.016",
description: "+3,1% minggu ini",
icon: TrendingUp,
},
]
export default function Home() {
return (
<main className="min-h-screen bg-muted/40 p-6 md:p-10">
<div className="mx-auto max-w-6xl">
<div className="mb-8">
<h2 className="text-3xl font-bold tracking-tight">
Ringkasan Operasional
</h2>
<p className="mt-2 text-muted-foreground">
Data pesanan hari ini dari seluruh area layanan Jakarta.
</p>
</div>
<section className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{stats.map((stat) => {
const Icon = stat.icon
return (
<StatCard
key={stat.title}
title={stat.title}
value={stat.value}
description={stat.description}
icon={<Icon className="size-4" />}
/>
)
})}
</section>
</div>
</main>
)
}
Jika lucide-react belum tersedia, pasang:
npm install lucide-react
Hasil yang Diharapkan
Halaman menampilkan empat kartu statistik. Pada layar kecil, kartu tersusun satu atau dua kolom. Pada layar besar, keempatnya muncul dalam satu baris.
Mengapa Langkah Ini Penting?
Pemisahan antara primitive dan komponen domain membuat arsitektur lebih terjaga.
Bandingkan dua folder berikut:
components/
├── ui/
│ ├── button.tsx
│ ├── card.tsx
│ └── badge.tsx
└── stat-card.tsx
Folder ui berisi bahan bangunan umum. stat-card.tsx berisi komponen yang memahami kebutuhan dasbor.
Pola ini mencegah components/ui/card.tsx dipenuhi logika omzet, pesanan, atau wilayah layanan yang seharusnya tidak menjadi tanggung jawab primitive kartu.
Step 6 — Menambahkan Tabel Pesanan
Tambahkan komponen tabel:
npx shadcn@latest add table
Buat components/orders-table.tsx:
import { Badge } from "@/components/ui/badge"
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table"
type OrderStatus = "Diproses" | "Dikirim" | "Selesai"
type Order = {
id: string
customer: string
area: string
total: string
status: OrderStatus
}
const orders: Order[] = [
{
id: "ORD-1048",
customer: "Rina",
area: "Tebet",
total: "Rp185.000",
status: "Diproses",
},
{
id: "ORD-1047",
customer: "Budi",
area: "Kemang",
total: "Rp240.000",
status: "Dikirim",
},
{
id: "ORD-1046",
customer: "Nadia",
area: "Menteng",
total: "Rp126.000",
status: "Selesai",
},
{
id: "ORD-1045",
customer: "Fajar",
area: "Cilandak",
total: "Rp315.000",
status: "Selesai",
},
]
function getStatusVariant(status: OrderStatus) {
if (status === "Selesai") return "default"
if (status === "Dikirim") return "secondary"
return "outline"
}
export function OrdersTable() {
return (
<div className="overflow-hidden rounded-xl border bg-card">
<Table>
<TableHeader>
<TableRow>
<TableHead>Pesanan</TableHead>
<TableHead>Pelanggan</TableHead>
<TableHead>Area</TableHead>
<TableHead>Status</TableHead>
<TableHead className="text-right">Total</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{orders.map((order) => (
<TableRow key={order.id}>
<TableCell className="font-medium">{order.id}</TableCell>
<TableCell>{order.customer}</TableCell>
<TableCell>{order.area}</TableCell>
<TableCell>
<Badge variant={getStatusVariant(order.status)}>
{order.status}
</Badge>
</TableCell>
<TableCell className="text-right">{order.total}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
)
}
Tambahkan ke app/page.tsx setelah bagian statistik:
import { OrdersTable } from "@/components/orders-table"
Lalu masukkan:
<section className="mt-8">
<div className="mb-4">
<h3 className="text-xl font-semibold">Pesanan Terbaru</h3>
<p className="text-sm text-muted-foreground">
Transaksi yang baru masuk dari area layanan aktif.
</p>
</div>
<OrdersTable />
</section>
Hasil yang Diharapkan
Di bawah kartu statistik akan muncul tabel pesanan. Setiap baris menampilkan nomor pesanan, pelanggan, area, status, dan total transaksi.
Mengapa Langkah Ini Penting?
Tabel bisnis sering berkembang menjadi komponen yang rumit. Nanti mungkin ada penyortiran, pencarian, pilihan baris, paginasi, dan tindakan massal.
Memulai dari tabel sederhana membantu tim menguji struktur data sebelum memasang solusi tabel yang lebih besar. Jangan langsung memakai tabel canggih jika kebutuhan saat ini hanya menampilkan sepuluh transaksi.
Masalah Responsif pada Tabel
Tabel lebar dapat melampaui layar ponsel. Bungkus tabel dengan kontainer yang dapat digulir:
<div className="overflow-x-auto">
<Table>{/* isi tabel */}</Table>
</div>
Untuk pengalaman seluler yang lebih baik, pertimbangkan:
-
Menyembunyikan kolom yang kurang penting.
-
Menampilkan detail lewat dialog.
-
Mengubah setiap baris menjadi kartu pada layar kecil.
-
Menjaga kolom tindakan tetap mudah disentuh.
Step 7 — Menambahkan Dialog untuk Pesanan Baru
Tambahkan komponen dialog, input, dan label:
npx shadcn@latest add dialog input label textarea
Buat components/create-order-dialog.tsx:
"use client"
import { useState } from "react"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
export function CreateOrderDialog() {
const [open, setOpen] = useState(false)
function handleSubmit(event: React. FormEvent<HTMLFormElement>) {
event.preventDefault()
const formData = new FormData(event.currentTarget)
const customer = formData.get("customer")
console.log("Pesanan dibuat untuk:", customer)
setOpen(false)
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button>Tambah pesanan</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-lg">
<form onSubmit={handleSubmit}>
<DialogHeader>
<DialogTitle>Tambah pesanan</DialogTitle>
<DialogDescription>
Masukkan data pelanggan dan alamat pengiriman.
</DialogDescription>
</DialogHeader>
<div className="grid gap-5 py-6">
<div className="grid gap-2">
<Label htmlFor="customer">Nama pelanggan</Label>
<Input
id="customer"
name="customer"
placeholder="Contoh: Siti Rahma"
required
/>
</div>
<div className="grid gap-2">
<Label htmlFor="phone">Nomor WhatsApp</Label>
<Input
id="phone"
name="phone"
type="tel"
placeholder="08xxxxxxxxxx"
required
/>
</div>
<div className="grid gap-2">
<Label htmlFor="address">Alamat pengiriman</Label>
<Textarea
id="address"
name="address"
placeholder="Nama jalan, nomor rumah, dan patokan"
required
/>
</div>
</div>
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => setOpen(false)}
>
Batal
</Button>
<Button type="submit">Simpan pesanan</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
)
}
Tambahkan komponen ke bagian atas halaman:
import { CreateOrderDialog } from "@/components/create-order-dialog"
Ubah bagian judul menjadi:
<div className="mb-8 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<h2 className="text-3xl font-bold tracking-tight">
Ringkasan Operasional
</h2>
<p className="mt-2 text-muted-foreground">
Data pesanan hari ini dari seluruh area layanan Jakarta.
</p>
</div>
<CreateOrderDialog />
</div>
Hasil yang Diharapkan
Saat tombol “Tambah pesanan” ditekan, dialog terbuka. Fokus berpindah ke dalam dialog dan pengguna dapat mengisi formulir.
Baca juga GitHub Copilot: Cara Mempercepat Kerja Tim
Setelah formulir dikirim:
-
Data pelanggan ditampilkan di konsol peramban.
-
Dialog tertutup.
-
Halaman tidak dimuat ulang.
Mengapa "use client" Diperlukan?
Komponen ini memakai useState dan peristiwa klik. Fitur tersebut berjalan di peramban, jadi berkas perlu ditandai sebagai Client Component.
Tanpa "use client", Next.js dapat menampilkan kesalahan karena hook React dipakai dalam Server Component.
Kesalahan yang Sering Terjadi
useState Tidak Bisa Dipakai
Pesan kesalahannya biasanya menyebut hook hanya dapat digunakan pada Client Component.
Pastikan baris pertama berkas adalah:
"use client"
Tidak boleh ada impor atau komentar sebelum direktif tersebut.
Tombol Pemicu Tidak Terlihat Benar
Gunakan asChild pada DialogTrigger:
<DialogTrigger asChild>
<Button>Tambah pesanan</Button>
</DialogTrigger>
Tanpa asChild, pemicu dapat menghasilkan elemen tombol di dalam tombol. Struktur HTML seperti itu tidak valid dan dapat mengganggu aksesibilitas.
Dialog Langsung Tertutup
Periksa apakah tombol di dalam formulir memiliki tipe yang benar. Tombol “Batal” harus memakai:
<Button type="button">Batal</Button>
Jika type tidak ditentukan, tombol dapat dianggap sebagai tombol kirim.
Step 8 — Menambahkan Validasi Formulir
Validasi HTML seperti required cukup untuk contoh sederhana, tetapi aplikasi produksi biasanya membutuhkan aturan yang lebih jelas. Kita bisa memakai React Hook Form dan Zod.
Pasang dependensi:
npm install react-hook-form zod @hookform/resolvers
Tambahkan komponen formulir:
npx shadcn@latest add form
Buat skema data:
import { z } from "zod"
export const orderSchema = z.object({
customer: z
.string()
.min(2, "Nama pelanggan minimal 2 karakter")
.max(80, "Nama pelanggan terlalu panjang"),
phone: z
.string()
.min(10, "Nomor WhatsApp terlalu pendek")
.max(15, "Nomor WhatsApp terlalu panjang")
.regex(/^[0-9+]+$/, "Gunakan angka atau tanda +"),
address: z
.string()
.min(10, "Alamat perlu ditulis lebih lengkap")
.max(300, "Alamat terlalu panjang"),
})
export type OrderFormValues = z.infer<typeof orderSchema>
Simpan sebagai lib/validations/order.ts.
Kemudian bentuk formulir dengan komponen shadcn/ui:
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import {
OrderFormValues,
orderSchema,
} from "@/lib/validations/order"
import { Button } from "@/components/ui/button"
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
export function CreateOrderForm() {
const form = useForm<OrderFormValues>({
resolver: zodResolver(orderSchema),
defaultValues: {
customer: "",
phone: "",
address: "",
},
})
function onSubmit(values: OrderFormValues) {
console.log(values)
}
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="grid gap-5"
>
<FormField
control={form.control}
name="customer"
render={({ field }) => (
<FormItem>
<FormLabel>Nama pelanggan</FormLabel>
<FormControl>
<Input
placeholder="Contoh: Siti Rahma"
autoComplete="name"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="phone"
render={({ field }) => (
<FormItem>
<FormLabel>Nomor WhatsApp</FormLabel>
<FormControl>
<Input
type="tel"
inputMode="tel"
placeholder="08xxxxxxxxxx"
autoComplete="tel"
{...field}
/>
</FormControl>
<FormDescription>
Nomor dipakai untuk konfirmasi pesanan.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="address"
render={({ field }) => (
<FormItem>
<FormLabel>Alamat pengiriman</FormLabel>
<FormControl>
<Textarea
placeholder="Nama jalan, nomor rumah, dan patokan"
autoComplete="street-address"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" disabled={form.formState.isSubmitting}>
{form.formState.isSubmitting
? "Menyimpan..."
: "Simpan pesanan"}
</Button>
</form>
</Form>
)
}
Hasil yang Diharapkan
Jika pengguna mengirim formulir kosong, pesan kesalahan muncul di bawah kolom terkait. Jika semua data valid, objek berikut muncul di konsol:
{
customer: "Siti Rahma",
phone: "081234567890",
address: "Jl. Tebet Barat Dalam, Jakarta Selatan"
}
Mengapa Validasi Perlu Dipisahkan?
Skema Zod menjadi kontrak data yang jelas. Aturan yang sama dapat dipakai kembali pada formulir, route handler, atau server action.
Namun, validasi di peramban tidak menggantikan validasi di server. Pengguna dapat melewati JavaScript peramban atau mengirim permintaan langsung ke API.
Aturan penting: validasi di klien membantu pengalaman pengguna, sedangkan validasi di server menjaga integritas dan keamanan data. Aplikasi produksi membutuhkan keduanya.
Menangani Nomor Telepon Indonesia
Nomor telepon tampak sederhana, tetapi formatnya beragam:
Baca juga Mengenal Astro 7.1: Framework JavaScript Ringan untuk Website
-
081234567890 -
+6281234567890 -
6281234567890 -
Nomor dengan spasi atau tanda hubung
Jangan membuat aturan terlalu ketat tanpa memahami kebutuhan bisnis. Normalisasi format di server, lalu simpan bentuk yang konsisten.
Contoh normalisasi sederhana:
export function normalizeIndonesianPhone(value: string) {
const cleaned = value.replace(/[^\d+]/g, "")
if (cleaned.startsWith("08")) {
return `+62${cleaned.slice(1)}`
}
if (cleaned.startsWith("62")) {
return `+${cleaned}`
}
return cleaned
}
Untuk sistem yang berhubungan dengan pembayaran, identitas, atau komunikasi penting, gunakan pengujian yang lebih lengkap.
Step 9 — Menambahkan Toast untuk Umpan Balik
Pengguna perlu tahu apakah tindakan berhasil atau gagal. Menutup dialog tanpa pesan dapat membuat orang bertanya-tanya apakah data benar-benar tersimpan.
Tambahkan komponen notifikasi yang tersedia pada versi shadcn/ui proyek Teman-Teman. Salah satu pilihan yang umum adalah Sonner:
npx shadcn@latest add sonner
Tambahkan Toaster pada app/layout.tsx:
import type { Metadata } from "next"
import { Toaster } from "@/components/ui/sonner"
import "./globals.css"
export const metadata: Metadata = {
title: "Dasbor Pesanan",
description: "Dasbor operasional pesanan bisnis lokal",
}
export default function RootLayout({
children,
}: Readonly<{
children: React. ReactNode
}>) {
return (
<html lang="id">
<body>
{children}
<Toaster richColors position="top-right" />
</body>
</html>
)
}
Panggil toast setelah formulir berhasil:
import { toast } from "sonner"
function onSubmit(values: OrderFormValues) {
console.log(values)
toast.success("Pesanan berhasil disimpan", {
description: `Pesanan atas nama ${values.customer} sudah masuk.`,
})
}
Tangani kegagalan dengan pesan yang bisa ditindaklanjuti:
toast.error("Pesanan belum tersimpan", {
description: "Periksa koneksi lalu coba lagi.",
})
Hasil yang Diharapkan
Setelah data valid dikirim, notifikasi muncul di kanan atas. Pesan menyebut bahwa pesanan berhasil disimpan dan menampilkan nama pelanggan.
Mengapa Umpan Balik Harus Spesifik?
Pesan “Berhasil” terlalu umum. Pengguna perlu tahu tindakan apa yang berhasil.
Lebih baik:
Pesanan berhasil disimpan
Pesanan atas nama Siti Rahma sudah masuk.
Daripada:
Sukses!
Pada aplikasi operasional, pesan yang jelas mengurangi tindakan ganda. Kasir tidak perlu menekan tombol simpan berulang kali karena ragu.
Step 10 — Menghubungkan Formulir dengan Server Action
Mencetak data ke konsol belum menghasilkan aplikasi nyata. Berikut contoh server action sederhana untuk menerima dan memvalidasi data.
Buat app/actions/orders.ts:
"use server"
import { orderSchema } from "@/lib/validations/order"
export type CreateOrderState = {
success: boolean
message: string
}
export async function createOrder(
input: unknown
): Promise<CreateOrderState> {
const parsed = orderSchema.safeParse(input)
if (!parsed.success) {
return {
success: false,
message: "Data pesanan belum valid.",
}
}
const order = parsed.data
// Simpan ke basis data di sini.
console.log("Data tervalidasi di server:", order)
return {
success: true,
message: "Pesanan berhasil dibuat.",
}
}
Panggil dari komponen klien:
import { createOrder } from "@/app/actions/orders"
async function onSubmit(values: OrderFormValues) {
const result = await createOrder(values)
if (!result.success) {
toast.error("Pesanan belum tersimpan", {
description: result.message,
})
return
}
toast.success("Pesanan berhasil disimpan")
form.reset()
}
Mengapa Validasi Diulang di Server?
Data dari klien tidak boleh langsung dipercaya. Walaupun formulir memakai Zod, orang tetap dapat mengirim permintaan buatan sendiri.
Server action harus:
-
Memvalidasi data.
-
Memeriksa hak akses.
-
Membersihkan atau menormalkan data.
-
Menyimpan data.
-
Mengembalikan respons yang aman.
Untuk contoh tutorial, penyimpanan basis data belum ditambahkan. Pada produk nyata, gunakan transaksi basis data jika satu tindakan mengubah beberapa tabel sekaligus.
Jangan Mengirim Kesalahan Internal ke Pengguna
Hindari:
Baca juga 9Router v0.5.35: Solusi Rate Limit AI Coding
return {
success: false,
message: error.stack,
}
Pesan tersebut dapat membocorkan struktur internal aplikasi. Catat detailnya di sistem pemantauan, lalu tampilkan pesan yang aman:
return {
success: false,
message: "Terjadi kendala saat menyimpan pesanan.",
}
Step 11 — Menambahkan Tema Gelap
Tema gelap bukan sekadar membalik latar menjadi hitam. Semua warna permukaan, teks, garis, grafik, dan fokus perlu tetap terbaca.
Pasang next-themes:
npm install next-themes
Buat components/theme-provider.tsx:
"use client"
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
export function ThemeProvider({
children,
...props
}: React. ComponentProps<typeof NextThemesProvider>) {
return (
<NextThemesProvider {...props}>
{children}
</NextThemesProvider>
)
}
Bungkus aplikasi di app/layout.tsx:
import { ThemeProvider } from "@/components/theme-provider"
import { Toaster } from "@/components/ui/sonner"
export default function RootLayout({
children,
}: Readonly<{
children: React. ReactNode
}>) {
return (
<html lang="id" suppressHydrationWarning>
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
<Toaster richColors position="top-right" />
</ThemeProvider>
</body>
</html>
)
}
Tambahkan dropdown atau tombol tema. Pasang dropdown:
npx shadcn@latest add dropdown-menu
Buat components/theme-toggle.tsx:
"use client"
import { Laptop, Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export function ThemeToggle() {
const { setTheme } = useTheme()
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
size="icon"
aria-label="Pilih tema"
>
<Sun className="size-4 dark:hidden" />
<Moon className="hidden size-4 dark:block" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
<Sun className="mr-2 size-4" />
Terang
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
<Moon className="mr-2 size-4" />
Gelap
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
<Laptop className="mr-2 size-4" />
Ikuti perangkat
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
Tempatkan ThemeToggle di bagian atas halaman.
Hasil yang Diharapkan
Pengguna dapat memilih:
-
Tema terang.
-
Tema gelap.
-
Tema yang mengikuti pengaturan perangkat.
Pilihan disimpan oleh next-themes, jadi tema tetap sama setelah halaman dimuat ulang.
Mengatasi Hydration Warning
Tema pengguna baru diketahui di peramban. Karena itu, hasil render server dan klien dapat berbeda.
Atribut berikut membantu menangani perubahan kelas tema:
<html lang="id" suppressHydrationWarning>
Namun, jangan memakai suppressHydrationWarning untuk menyembunyikan semua masalah render. Gunakan hanya pada bagian yang memang memiliki perbedaan terkontrol.
Step 12 — Mengatur Struktur Proyek agar Mudah Dipelihara
Setelah komponen bertambah, struktur folder mulai berpengaruh terhadap produktivitas tim. Salah satu susunan yang cukup aman adalah:
app/
├── actions/
│ └── orders.ts
├── globals.css
├── layout.tsx
└── page.tsx
components/
├── ui/
│ ├── badge.tsx
│ ├── button.tsx
│ ├── card.tsx
│ ├── dialog.tsx
│ ├── input.tsx
│ └── table.tsx
├── create-order-dialog.tsx
├── create-order-form.tsx
├── orders-table.tsx
├── stat-card.tsx
├── theme-provider.tsx
└── theme-toggle.tsx
lib/
├── validations/
│ └── order.ts
└── utils.ts
Aturan Praktis Penempatan Komponen
Gunakan components/ui untuk:
-
Button.
-
Input.
-
Dialog.
-
Badge.
-
Card.
-
Primitive lain yang tidak memahami bisnis.
Gunakan folder komponen biasa untuk:
-
Tabel pesanan.
-
Kartu omzet.
-
Formulir pelanggan.
-
Navigasi dasbor.
-
Pemilih cabang.
-
Ringkasan pengiriman.
Gunakan lib untuk:
-
Validasi.
-
Pemformatan.
-
Klien basis data.
-
Fungsi autentikasi.
-
Utilitas yang tidak merender antarmuka.
Mengapa Jangan Mengubah Semua Primitive?
Karena kode shadcn/ui berada di proyek, godaan pertama adalah mengubah setiap berkas. Itu tidak selalu perlu.
MUGHU menyarankan urutan keputusan berikut:
-
Coba susun komponen yang ada.
-
Gunakan
classNameuntuk penyesuaian lokal. -
Tambahkan
variantjika pola dipakai berulang. -
Ubah primitive jika perilaku dasarnya memang perlu berubah.
-
Dokumentasikan perubahan yang memengaruhi seluruh produk.
Pendekatan ini menjaga komponen tetap mudah dipahami.
Baca juga Cursor Composer 3: AI Coding 1,5T Parameter Segera Rilis
Step 13 — Membuat Variant Tombol Khusus
Misalnya, bisnis membutuhkan tombol untuk tindakan berisiko. shadcn/ui biasanya sudah menyediakan variant destructive, tetapi kita dapat mempelajari cara variant bekerja.
Buka components/ui/button.tsx. Teman-Teman akan melihat pola variant yang kurang lebih seperti ini:
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
destructive:
"bg-destructive text-white shadow-xs hover:bg-destructive/90",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground",
link:
"text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3",
lg: "h-10 rounded-md px-6",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
Tambahkan variant hanya jika benar-benar mewakili pola desain yang stabil. Contohnya, tombol persetujuan:
approval:
"bg-emerald-600 text-white shadow-xs hover:bg-emerald-700 focus-visible:ring-emerald-600/30",
Lalu gunakan:
<Button variant="approval">
Setujui pesanan
</Button>
Mengapa Variant Lebih Baik daripada Kelas Berulang?
Bandingkan:
<Button className="bg-emerald-600 text-white hover:bg-emerald-700">
Setujui
</Button>
Jika pola tersebut muncul di 20 tempat, perubahan desain harus dilakukan 20 kali. Variant memusatkan keputusan visual.
Namun, jangan membuat variant untuk setiap konteks kecil:
greenButton
dashboardGreenButton
orderGreenButton
specialGreenButton
Nama variant harus menjelaskan makna, bukan sekadar warnanya. approval lebih tahan terhadap perubahan desain dibandingkan green.
Step 14 — Menambahkan Status Kosong dan Skeleton
Aplikasi tidak selalu memiliki data. Tabel kosong tanpa penjelasan dapat terlihat seperti kerusakan.
Tambahkan skeleton:
npx shadcn@latest add skeleton
Buat components/dashboard-skeleton.tsx:
import { Skeleton } from "@/components/ui/skeleton"
export function DashboardSkeleton() {
return (
<div className="space-y-8">
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
{Array.from({ length: 4 }).map((_, index) => (
<div
key={index}
className="rounded-xl border bg-card p-6"
>
<Skeleton className="h-4 w-24" />
<Skeleton className="mt-4 h-8 w-32" />
<Skeleton className="mt-3 h-3 w-40" />
</div>
))}
</div>
<div className="rounded-xl border bg-card p-6">
<Skeleton className="h-6 w-40" />
<Skeleton className="mt-6 h-10 w-full" />
<Skeleton className="mt-3 h-10 w-full" />
<Skeleton className="mt-3 h-10 w-full" />
</div>
</div>
)
}
Buat status kosong untuk pesanan:
import { Inbox } from "lucide-react"
import { Button } from "@/components/ui/button"
export function EmptyOrders() {
return (
<div className="flex min-h-72 flex-col items-center justify-center rounded-xl border border-dashed bg-card p-8 text-center">
<div className="flex size-12 items-center justify-center rounded-full bg-muted">
<Inbox className="size-5 text-muted-foreground" />
</div>
<h3 className="mt-4 font-semibold">
Belum ada pesanan
</h3>
<p className="mt-2 max-w-sm text-sm text-muted-foreground">
Pesanan baru akan muncul di sini. Tambahkan pesanan
secara manual jika pelanggan memesan lewat telepon.
</p>
<Button className="mt-5">
Tambah pesanan
</Button>
</div>
)
}
Mengapa Status Antarmuka Ini Penting?
Satu halaman setidaknya perlu memikirkan empat kondisi:
Kondisi | Tujuan Antarmuka |
|---|---|
Memuat | Menunjukkan bahwa proses sedang berjalan |
Berhasil | Menampilkan data atau konfirmasi |
Kosong | Menjelaskan bahwa belum ada data |
Gagal | Menjelaskan masalah dan langkah berikutnya |
Komponen yang hanya dirancang untuk kondisi berhasil biasanya terasa rapuh saat dipakai di dunia nyata.
Hindari Skeleton yang Terlalu Lama
Skeleton berguna untuk proses singkat. Jika permintaan membutuhkan waktu lama, tampilkan informasi yang lebih jelas atau opsi untuk mencoba ulang.
Animasi tanpa penjelasan selama puluhan detik tidak membuat sistem terasa cepat. Ia hanya membuat pengguna menunggu dengan dekorasi.
Step 15 — Menguji Aksesibilitas dan Navigasi Papan Ketik
Komponen yang memakai primitive aksesibel tetap perlu diuji dalam konteks halaman. Susunan komponen, teks, dan gaya khusus dapat menciptakan masalah baru.
Daftar Uji Papan Ketik
Coba alur berikut tanpa mouse:
-
Tekan
Tabsampai tombol “Tambah pesanan” terfokus. -
Tekan
Enter. -
Pastikan dialog terbuka.
-
Tekan
Tabuntuk berpindah antarbidang. -
Tekan
Shift + Tabuntuk bergerak mundur. -
Tekan
Escapeuntuk menutup dialog. -
Pastikan fokus kembali ke tombol pemicu.
Periksa Label Formulir
Semua input harus memiliki label yang jelas:
<Label htmlFor="phone">Nomor WhatsApp</Label>
<Input id="phone" name="phone" />
Placeholder bukan pengganti label. Placeholder menghilang saat pengguna mulai mengetik dan sering memiliki kontras rendah.
Baca juga GPT Image 2: Panduan Lengkap API Gambar AI OpenAI
Periksa Nama Tombol Ikon
Tombol yang hanya memiliki ikon membutuhkan nama aksesibel:
<Button
variant="outline"
size="icon"
aria-label="Pilih tema"
>
<Sun className="size-4" />
</Button>
Tanpa aria-label, pembaca layar mungkin hanya mengumumkan “button” tanpa menjelaskan fungsinya.
Periksa Kontras dan Fokus
Pastikan fokus terlihat:
className="focus-visible:ring-2 focus-visible:ring-ring"
Jangan menghapus outline tanpa memberikan indikator pengganti:
/* Hindari jika tidak ada pengganti yang jelas */
button:focus {
outline: none;
}
Step 16 — Menjaga Performa Aplikasi
shadcn/ui memberi kendali besar, tetapi performa tetap bergantung pada cara aplikasi dibangun.
Tambahkan Komponen Secukupnya
Jangan memasang seluruh katalog hanya karena tersedia. Tambahkan saat diperlukan:
npx shadcn@latest add button card dialog
Pendekatan bertahap membuat repositori lebih mudah diperiksa dan mengurangi kode yang tidak dipakai.
Jaga Client Component Tetap Kecil
Jangan menandai seluruh halaman dengan "use client" hanya karena satu tombol membutuhkan state.
Lebih baik:
Server page
├── Server-rendered heading
├── Server-rendered statistics
└── Client dialog button
Daripada:
Entire dashboard as one Client Component
Server Component membantu mengurangi JavaScript yang dikirim ke peramban dan dapat mengambil data langsung di server.
Hindari Impor Ikon Berlebihan
Impor ikon yang dipakai:
import { Moon, Sun } from "lucide-react"
Hindari pola yang mengimpor seluruh paket sebagai objek jika tidak diperlukan.
Ukur, Jangan Menebak
Gunakan alat peramban dan laporan pembangunan untuk memeriksa:
-
Ukuran bundel.
-
Pergeseran tata letak.
-
Waktu respons server.
-
Waktu interaksi.
-
Permintaan jaringan.
-
Komponen yang dirender ulang terlalu sering.
Optimasi tanpa pengukuran dapat menghabiskan waktu pada bagian yang dampaknya kecil.
Step 17 — Menangani Pembaruan Komponen
Model Open Code memiliki konsekuensi: komponen lokal tidak selalu mengikuti perubahan terbaru secara otomatis.
Ini sekaligus kekuatan dan tanggung jawab.
Kelebihannya
-
Pembaruan paket tidak tiba-tiba mengubah tampilan komponen lokal.
-
Tim dapat mengendalikan waktu migrasi.
-
Penyesuaian tetap berada di repositori.
-
Semua perubahan dapat ditinjau melalui Git.
Kekurangannya
-
Perbaikan dari sumber tidak selalu masuk otomatis.
-
Tim perlu membandingkan perubahan.
-
Komponen yang banyak dimodifikasi lebih sulit diselaraskan.
-
Dokumentasi internal menjadi penting.
Strategi Pembaruan yang Aman
-
Catat komponen yang telah dimodifikasi.
-
Buat cabang khusus pembaruan.
-
Periksa perubahan CLI sebelum menimpa berkas.
-
Gunakan
git diff. -
Jalankan tes visual dan interaksi.
-
Uji tema terang serta gelap.
-
Gabungkan hanya setelah perubahan dipahami.
Sebelum menjalankan perintah yang berpotensi menimpa berkas, simpan perubahan:
git add .
git commit -m "chore: save UI before component update"
Setelah pembaruan:
git diff
Jangan menyetujui perubahan hanya karena aplikasi masih bisa dikompilasi. Periksa perilaku fokus, ukuran, variant, dan tampilan responsif.
Implementasi Studi Kasus: Hasil yang Dicapai
Dalam studi kasus dasbor pesanan, perpindahan ke shadcn/ui dinilai dari sisi teknis dan operasional. Angka berikut merupakan ilustrasi target yang masuk akal, bukan tolok ukur universal.
Metrik | Sebelum Standardisasi | Setelah Implementasi | Perubahan |
|---|---|---|---|
Waktu membuat halaman operasional | 3–4 hari | 1,5–2,5 hari | Lebih cepat sekitar 35–50% |
Variasi tombol tak terdokumentasi | 11 variasi | 5 variant resmi | Berkurang 55% |
Kode gaya berulang | Tinggi | Lebih terpusat | Berkurang secara nyata |
Masalah fokus pada dialog | Sering ditemukan | Lebih terkendali | Risiko turun |
Waktu perubahan warna merek | Beberapa jam | Kurang dari 1 jam | Lebih efisien |
Komponen domain yang bisa dipakai ulang | Sedikit | 8 komponen | Konsistensi meningkat |
Perbaikan tersebut tidak terjadi hanya karena memasang CLI. Dampak terbesar datang dari keputusan arsitektur:
-
Token ditetapkan lebih awal.
-
Primitive dan komponen bisnis dipisahkan.
-
Variant diberi nama berdasarkan makna.
-
Komponen hanya ditambahkan saat dibutuhkan.
-
Kondisi kosong, memuat, gagal, dan berhasil dirancang bersama.
-
Perubahan komponen ditinjau melalui Git.
Nilai Bisnis yang Terlihat
Bagi tim produk, manfaat utamanya bukan sekadar kartu yang lebih cantik. Nilainya muncul dalam bentuk:
-
Waktu implementasi fitur lebih mudah diperkirakan.
-
Peninjauan desain lebih cepat.
-
Pengembang tidak mengulang keputusan visual.
-
Masalah konsistensi berkurang sebelum pengujian.
-
Perubahan merek dapat diterapkan melalui token.
-
Tim memiliki fondasi yang dapat berkembang bersama produk.
Untuk agensi web di Jakarta, Bandung, Surabaya, Yogyakarta, atau kota lain, pola ini juga membantu saat menangani beberapa proyek klien. Fondasi dapat digunakan ulang, sementara warna, tipografi, dan komponen domain tetap disesuaikan untuk tiap merek.
Kesalahan Umum Saat Memakai shadcn/ui
Menganggap shadcn/ui sebagai Paket yang Tidak Boleh Disentuh
Tujuan Open Code adalah memberi kendali kepada tim. Teman-Teman boleh mengubah komponen.
Namun, perubahan sebaiknya punya alasan jelas. Jangan mengubah struktur hanya karena ingin membuat kode terlihat berbeda.
Menaruh Semua Komponen di components/ui
Folder components/ui bukan tempat untuk seluruh aplikasi.
Kurang tepat:
components/ui/order-payment-summary.tsx
components/ui/jakarta-delivery-map.tsx
components/ui/customer-loyalty-card.tsx
Lebih rapi:
components/ui/card.tsx
components/orders/order-payment-summary.tsx
components/delivery/jakarta-delivery-map.tsx
components/customers/customer-loyalty-card.tsx
Memasang Terlalu Banyak Komponen Sekaligus
Memasang semua komponen membuat repositori penuh berkas yang belum tentu digunakan. Tim juga menjadi sulit membedakan mana komponen aktif dan mana yang sekadar ikut terpasang.
Tambahkan sesuai kebutuhan fitur.
Mengubah Warna Tetap di Banyak Tempat
Hindari:
className="bg-[#0f7a45] text-[#ffffff]"
Jika warna tersebut mewakili merek, letakkan pada token:
className="bg-primary text-primary-foreground"
Mengandalkan Placeholder sebagai Label
Kurang tepat:
<Input placeholder="Nomor telepon" />
Lebih baik:
<Label htmlFor="phone">Nomor telepon</Label>
<Input id="phone" placeholder="08xxxxxxxxxx" />
Melupakan Kondisi Gagal
Jangan hanya membuat toast sukses. API dapat gagal karena jaringan, sesi kedaluwarsa, validasi server, atau basis data.
try {
const result = await createOrder(values)
if (!result.success) {
toast.error("Pesanan belum tersimpan", {
description: result.message,
})
return
}
toast.success("Pesanan berhasil disimpan")
} catch {
toast.error("Koneksi bermasalah", {
description: "Periksa jaringan lalu coba lagi.",
})
}
Mempercayai Semua Registry Pihak Ketiga
Ekosistem shadcn/ui memiliki banyak komponen, blok, dan template dari komunitas. Ada yang gratis, berbayar, terbuka, atau memakai lisensi khusus.
Sebelum memasang komponen pihak ketiga:
-
Baca sumber kodenya.
-
Periksa dependensi tambahan.
-
Pastikan lisensi sesuai.
-
Uji aksesibilitas.
-
Periksa permintaan jaringan.
-
Hindari kode yang meminta kredensial tanpa alasan jelas.
-
Tinjau apakah komponen cocok dengan versi React dan Tailwind proyek.
Katalog komunitas seperti Cult UI dan penyedia blok lain dapat mempercepat pekerjaan, tetapi tanggung jawab keamanan tetap berada pada tim yang memasukkannya ke produk.
Troubleshooting shadcn/ui
Modul @/components/ui/button Tidak Ditemukan
Periksa apakah komponen sudah ditambahkan:
npx shadcn@latest add button
Pastikan berkas tersedia:
components/ui/button.tsx
Periksa pula alias di tsconfig.json dan components.json.
cn Tidak Ditemukan
Komponen shadcn/ui sering memakai fungsi cn dari lib/utils.ts.
Contoh:
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
Jika berkas hilang, periksa hasil inisialisasi atau buat kembali dengan dependensi yang sesuai:
npm install clsx tailwind-merge
Gaya Komponen Tidak Muncul
Periksa:
-
CSS global diimpor di layout.
-
Tailwind berjalan.
-
Variabel tema tersedia.
-
Jalur komponen benar.
-
Server pengembangan sudah dimulai ulang.
-
Tidak ada kesalahan sintaks di CSS.
Jalankan:
npm run dev
Jika masalah tetap ada, hapus cache pembangunan:
rm -rf .next
npm run dev
Pada PowerShell:
Remove-Item -Recurse -Force .next
npm run dev
Kelas Dinamis Tailwind Tidak Terdeteksi
Pola berikut dapat bermasalah:
<div className={`bg-${color}-500`} />
Tailwind mungkin tidak dapat menemukan nama kelas lengkap saat memindai sumber.
Gunakan pemetaan eksplisit:
const colorClasses = {
green: "bg-green-500",
red: "bg-red-500",
blue: "bg-blue-500",
}
<div className={colorClasses[color]} />
Hydration Mismatch
Penyebab umum:
-
Membaca
windowsaat render server. -
Menggunakan tanggal atau angka acak langsung saat render.
-
Tema server dan klien berbeda.
-
Struktur HTML tidak valid.
-
Tombol berada di dalam tombol.
Hindari:
<p>{Math.random()}</p>
Jika nilai hanya tersedia di peramban, baca setelah komponen terpasang atau pindahkan logika ke Client Component yang tepat.
Dialog Tidak Bisa Dibuka
Periksa bahwa komponen interaktif berada dalam Client Component jika memakai state. Pastikan juga DialogTrigger berada di dalam Dialog.
<Dialog>
<DialogTrigger asChild>
<Button>Buka</Button>
</DialogTrigger>
<DialogContent>Isi</DialogContent>
</Dialog>
Variant TypeScript Ditolak
Jika Teman-Teman menambahkan variant baru, pastikan variant dimasukkan ke konfigurasi buttonVariants dan tipe tombol masih merujuk ke konfigurasi tersebut.
Mulai ulang server TypeScript atau penyunting kode bila tipe lama masih tersimpan.
Tampilan Berbeda Setelah Menyalin Contoh Lama
shadcn/ui berkembang dari waktu ke waktu. Nama kelas, dependensi, struktur tema, dan implementasi komponen dapat berubah.
Utamakan dokumentasi yang sesuai dengan versi proyek. Jangan mencampur contoh lama dan konfigurasi terbaru tanpa memeriksa perbedaannya.
Tips Praktis agar shadcn/ui Tetap Rapi
Tetapkan Token Sebelum Memoles Halaman
Sebelum mengubah setiap kartu, sepakati:
-
Warna utama.
-
Warna destruktif.
-
Warna teks sekunder.
-
Radius.
-
Bayangan.
-
Jarak dasar.
-
Ukuran judul.
-
Aturan fokus.
Keputusan ini mengurangi revisi kecil yang berulang.
Buat Daftar Variant yang Disetujui
Contoh dokumentasi internal:
Komponen | Variant | Penggunaan |
|---|---|---|
Button |
| Tindakan utama |
Button |
| Tindakan pendamping |
Button |
| Tindakan netral |
Button |
| Tindakan ringan |
Button |
| Menghapus atau membatalkan permanen |
Badge |
| Status aktif |
Badge |
| Status informatif |
Badge |
| Status menunggu |
Daftar sederhana ini menjaga desainer dan pengembang memakai bahasa visual yang sama.
Bungkus Pola Bisnis, Bukan Semua Elemen
Buat komponen jika pola:
-
Dipakai berulang.
-
Memiliki aturan bisnis.
-
Sulit ditulis dengan benar.
-
Membutuhkan pengujian khusus.
-
Perlu konsistensi lintas halaman.
Jangan membuat pembungkus yang tidak memberi nilai:
function MyButton(props) {
return <Button {...props} />
}
Pembungkus seperti itu hanya menambah lapisan tanpa menyederhanakan penggunaan.
Gunakan Git sebagai Pengaman
Sebelum menambah atau memperbarui komponen:
git status
git add .
git commit -m "chore: save work before adding UI component"
Setelah CLI berjalan:
git diff
Karena kode disalin ke proyek, git diff adalah alat penting untuk memahami apa yang berubah.
Dokumentasikan Keputusan, Bukan Hanya Komponen
Dokumentasi yang berguna menjelaskan:
-
Kapan memakai dialog dan kapan memakai sheet.
-
Kapan tombol harus
destructive. -
Cara menulis pesan kesalahan.
-
Aturan ukuran target sentuh.
-
Kapan tabel berubah menjadi kartu di ponsel.
-
Bagaimana format rupiah dan tanggal ditampilkan.
Komponen saja tidak cukup untuk membentuk sistem desain. Sistem desain juga membutuhkan aturan penggunaan.
Ulasan Seimbang: Kelebihan dan Kekurangan shadcn/ui
Kelebihan
-
Kepemilikan kode penuh. Tim dapat memeriksa dan mengubah implementasi.
-
Tampilan awal yang matang. Banyak komponen sudah terlihat rapi tanpa banyak penyesuaian.
-
Mudah disusun. Bagian komponen dapat digabungkan sesuai kebutuhan.
-
Cocok untuk sistem desain. Token dan variant dapat distandardisasi.
-
Mendukung aksesibilitas. Primitive dasarnya menangani banyak detail interaksi.
-
Ramah terhadap AI. Kode lokal lebih mudah dianalisis dan disunting.
-
Ekosistem berkembang. Tersedia registry, blok, template, serta contoh komunitas.
-
Risiko perubahan visual mendadak lebih rendah. Kode lokal tidak otomatis berubah hanya karena paket sumber diperbarui.
Kekurangan
-
Tim bertanggung jawab memelihara kode lokal.
-
Pembaruan tidak selalu otomatis masuk.
-
Penyesuaian tanpa aturan dapat menciptakan kekacauan baru.
-
Tidak semua kebutuhan bisnis memiliki komponen siap pakai.
-
Tailwind CSS tetap perlu dipahami.
-
Kualitas registry pihak ketiga tidak seragam.
-
Kepemilikan kode dapat menambah beban peninjauan keamanan.
-
Proyek kecil sekali mungkin tidak membutuhkan seluruh pola sistem desain.
Siapa yang Paling Cocok Memakainya?
shadcn/ui cocok untuk:
-
Tim React dan Next.js.
-
Produk SaaS.
-
Dasbor internal.
-
Situs bisnis dengan identitas visual khusus.
-
Agensi yang membutuhkan fondasi antarmuka fleksibel.
-
Produk yang akan berkembang dalam jangka panjang.
-
Tim yang terbiasa meninjau dan memelihara kode.
-
Proyek dengan alur kerja AI-assisted development.
Siapa yang Sebaiknya Memilih Pilihan Lain?
Pertimbangkan pustaka lain jika:
-
Tim ingin semua pembaruan komponen datang melalui versi paket.
-
Produk harus mengikuti Material Design secara ketat.
-
Tim tidak ingin menyentuh implementasi komponen.
-
Prototipe hanya dipakai sebentar dan kecepatan mutlak menjadi prioritas.
-
Proyek tidak memakai React.
-
Kapasitas pemeliharaan kode sangat terbatas.
Perbandingan Sumber Komponen dan Template shadcn
Ekosistem shadcn/ui mencakup sumber resmi dan penyedia komunitas. Fungsinya tidak selalu sama.
Sumber | Fokus | Cocok untuk | Hal yang Perlu Diperiksa |
|---|---|---|---|
shadcn/ui resmi | Primitive dan sistem distribusi | Fondasi proyek | Versi dokumentasi |
Registry komunitas | Komponen tambahan | Kebutuhan khusus | Kualitas kode dan lisensi |
Template terbuka | Proyek siap dikloning | Validasi ide dan percepatan awal | Arsitektur dan pemeliharaan |
Blok premium | Bagian halaman siap pakai | Agensi dan tim dengan deadline ketat | Hak penggunaan komersial |
Koleksi animasi | Efek dan elemen pemasaran | Landing page | Performa dan aksesibilitas |
Generator tema | Token warna dan radius | Sistem desain | Kontras dan konsistensi merek |
Sumber resmi sebaiknya menjadi acuan utama untuk konsep dan implementasi inti. Contoh komunitas dapat mempercepat pekerjaan, tetapi jangan menyalin kode tanpa peninjauan.
Pelajaran Utama dari Implementasi
Open Code Bukan Berarti Bebas Tanpa Aturan
Kebebasan mengubah kode harus disertai disiplin. Tanpa token, dokumentasi, dan peninjauan, komponen lokal dapat bercabang menjadi banyak variasi yang sulit dirawat.
ROI Datang dari Standardisasi
Penghematan waktu bukan berasal dari satu tombol yang selesai dalam lima menit. Nilainya muncul ketika puluhan halaman memakai pola yang sama dan perubahan desain dapat diterapkan dengan risiko lebih rendah.
Primitive dan Domain Harus Dipisahkan
Button tidak perlu tahu soal pesanan. OrdersTable boleh memahami status bisnis, format rupiah, dan area layanan.
Pemisahan ini membuat komponen lebih mudah diuji, digunakan ulang, dan dipahami anggota tim.
Aksesibilitas Tetap Perlu Diuji
Primitive yang baik memberi titik awal kuat, tetapi keputusan aplikasi dapat merusaknya. Label yang hilang, warna yang lemah, atau tombol ikon tanpa nama tetap menjadi tanggung jawab tim.
Template Bukan Produk Jadi
Template dapat mempercepat tahap awal, tetapi masih perlu:
-
Pemeriksaan keamanan.
-
Penyesuaian identitas.
-
Penghapusan kode yang tidak dipakai.
-
Validasi lisensi.
-
Pengujian performa.
-
Pengujian aksesibilitas.
-
Integrasi data nyata.
Metrik Harus Dibaca Sesuai Konteks
Pengurangan waktu pengembangan sebesar 30% pada satu tim tidak menjamin hasil yang sama pada tim lain. Dampak dipengaruhi pengalaman React, tingkat penyesuaian, kualitas desain, dan kompleksitas produk.
Gunakan metrik internal seperti:
-
Waktu pembuatan fitur.
-
Jumlah variasi tak resmi.
-
Jumlah masalah aksesibilitas.
-
Waktu peninjauan desain.
-
Frekuensi regresi visual.
-
Ukuran JavaScript klien.
-
Tingkat penggunaan ulang komponen.
Langkah Lanjutan Setelah Dasbor Berfungsi
Setelah fondasi berjalan, pengembangan dapat diteruskan secara bertahap:
-
Hubungkan server action dengan basis data.
-
Tambahkan autentikasi dan otorisasi.
-
Buat paginasi untuk tabel pesanan.
-
Tambahkan penyortiran dan pencarian.
-
Buat filter berdasarkan area Jakarta.
-
Tambahkan format tanggal dan rupiah yang konsisten.
-
Bangun halaman detail pesanan.
-
Tambahkan konfirmasi sebelum tindakan destruktif.
-
Tulis tes untuk validasi dan fungsi normalisasi.
-
Jalankan audit aksesibilitas.
-
Ukur performa halaman produksi.
-
Dokumentasikan token dan variant yang disetujui.
Untuk formatter rupiah, gunakan API bawaan JavaScript:
export function formatRupiah(value: number) {
return new Intl. NumberFormat("id-ID", {
style: "currency",
currency: "IDR",
maximumFractionDigits: 0,
}).format(value)
}
Contoh pemakaian:
<p>{formatRupiah(8450000)}</p>
Hasil yang diharapkan:
Rp8.450.000
Untuk tanggal Indonesia:
export function formatIndonesianDate(value: Date) {
return new Intl. DateTimeFormat("id-ID", {
day: "numeric",
month: "long",
year: "numeric",
}).format(value)
}
Contoh hasil:
11 Juli 2026
Tambahkan pengujian sederhana agar format tidak berubah tanpa sengaja:
import { describe, expect, it } from "vitest"
import { formatRupiah } from "@/lib/formatters"
describe("formatRupiah", () => {
it("memformat angka sebagai mata uang rupiah", () => {
expect(formatRupiah(8450000)).toContain("8.450.000")
})
})
Dengan fondasi tersebut, proyek sudah memiliki komponen lokal, token tema, dialog, formulir tervalidasi, notifikasi, tema gelap, tabel operasional, serta struktur yang siap dikembangkan menjadi aplikasi produksi.
Kesimpulan
shadcn menawarkan pendekatan praktis untuk membangun antarmuka React yang konsisten tanpa mengorbankan kendali atas kode. Komponen lokal, token tema, validasi formulir, dialog, notifikasi, dan dukungan tema gelap dapat mempercepat pembuatan dasbor sekaligus menjaga arsitektur tetap mudah dipahami dan dikembangkan.
Namun, hasil akhirnya tetap bergantung pada kualitas implementasi. Template perlu disesuaikan dengan kebutuhan bisnis, termasuk format rupiah, tanggal Indonesia, alur pesanan, dan area layanan Jakarta. Aksesibilitas, keamanan, performa, pengujian, serta pemisahan komponen generik dari logika domain juga harus menjadi bagian dari proses, bukan pemeriksaan tambahan menjelang peluncuran.
Gunakan dokumentasi resmi shadcn sebagai rujukan teknis, lalu ukur dampaknya berdasarkan metrik proyek Anda sendiri. Mulailah dari fondasi yang sederhana, uji setiap keputusan, dan kembangkan sistem komponen secara disiplin hingga siap mendukung produk nyata.
Referensi
Shadcn UI. (2026). The foundation for your design system.
Shadcn UI. (2026). Introduction.
GitHub. (2026). Shadcn UI: Beautifully designed, accessible components.
Shadcn. (2026). Free React and shadcn/ui templates.
Shadcn. (2026). The AI-native shadcn/ui component library for React.
GitHub. (2026). Shadcn.
Shadcn Studio. (2026). Shadcn UI components, blocks, and templates.
Cult UI. (2026). Shadcn UI components, blocks, and templates.
GeeksforGeeks. (2026). What is ShadCN and why is it used?
Shadcn. (2026). Shadcn.
Komentar (0)
Belum ada komentar. Jadilah yang pertama berbagi pendapat!
Tinggalkan komentar