# shadcn/ui: Komponen Copy-Paste sebagai Sebuah Arsitektur

> Mengapa model own-your-code dari shadcn/ui mengungguli dependensi komponen, dan bagaimana primitive Radix serta token Tailwind berkembang menjadi design system yang nyata.

URL: https://www.matthewswong.com/id/blog/shadcn-ui-component-architecture/

- Author: Matthews Wong (https://www.matthewswong.com/id/about/)
- Published: 2026-07-05
- Updated: 2026-07-05
- Category: Web Dev

Setiap beberapa tahun sebuah component library merilis versi besar, dan setiap tim yang bergantung padanya mewarisi sore yang sama: membaca changelog, menulis ulang theme override, lalu menyadari tombol yang tidak pernah disentuh kini tampil bergeser dua piksel. Itulah biaya dari menginstal keputusan desain orang lain sebagai dependensi.

shadcn/ui mengambil posisi yang berbeda. Alih-alih mengirimkan paket yang diimpor, ia memberikan kode sumber asli setiap komponen dan membiarkan sebuah CLI menyalin kode itu langsung ke repositori kita sendiri. Hasilnya bukan sekadar library yang lebih ringkas, melainkan arsitektur yang sepenuhnya berbeda, dibangun di atas primitive Radix UI untuk perilaku komponen dan token Tailwind CSS untuk theming, dan ini mengubah cara sebuah design system berkembang seiring bertambahnya ukuran codebase.

## Bukan Component Library, Melainkan Model Distribusi

Dokumentasi shadcn/ui menyatakan posisinya secara terang-terangan: ini bukan component library, ini adalah cara membangun component library milik kita sendiri. Menjalankan perintah add pada CLI tidak menambahkan entri di package.json yang mengarah ke bundel terkompilasi. Perintah itu menulis sebuah file TypeScript biasa ke folder components kita, sepenuhnya dapat dibaca, sepenuhnya milik kita, tanpa versi yang perlu dilacak dan tanpa kotak hitam yang harus dilawan ketika kebutuhan desain tidak sesuai dengan asumsi library tersebut.

Library tradisional seperti Material UI atau Ant Design menyelesaikan masalah yang berbeda: perilaku yang konsisten di antara ribuan pengguna, dicapai lewat prop API dan objek theme yang harus tetap backward compatible. Batasan backward compatibility itu justru yang membuat proses upgrade menyakitkan. shadcn/ui menghindari batasan tersebut dengan tidak pernah menjanjikan kompatibilitas sejak awal, karena setelah langkah penyalinan tidak ada lagi paket bersama yang perlu dijaga kompatibilitasnya.

- Kode terbuka: komponen mendarat sebagai file sumber biasa di bawah components/ui, sehingga editor, linter, atau alat code review apa pun memperlakukannya seperti bagian lain dari codebase, bukan seperti keluaran node_modules yang tertutup.
- Tidak ada versi yang perlu dilawan: kode dibekukan pada saat kita menyalinnya. Rilis shadcn/ui di masa depan tidak bisa diam-diam mengubah tampilan tombol kita, karena tombol itu bukan lagi tombol milik shadcn/ui.
- Komposisi lebih dulu: setiap primitive memakai konvensi yang dapat diprediksi, prop asChild untuk polimorfisme dan class-variance-authority untuk variant, sehingga begitu tim memahami satu komponen, komponen lain terasa serupa saat dibaca.

> CLI tetap berguna meski kode sudah menjadi milik kita. Perlakukan sebagai alat scaffolding yang dijalankan sekali per komponen, bukan package manager yang dijalankan setiap kali install. Menjalankan ulang add pada komponen yang sudah dikustomisasi akan menimpa perubahan kita, jadi commit dulu sebelum menjalankannya lagi.

## Dibangun di Atas Primitive Radix untuk Aksesibilitas

Logika interaksi adalah bagian tersulit dari sebuah komponen, bukan gaya visualnya. Radix UI Primitives menyediakan logika itu sebagai building block yang tidak berstyle namun aksesibel, mengikuti pola penulisan WAI-ARIA, menangani navigasi keyboard, penjebakan fokus di dalam dialog, dan peran yang benar untuk pembaca layar. shadcn/ui membungkus setiap primitive Radix dengan utility class Tailwind, sehingga pekerjaan aksesibilitas diwariskan bukan diimplementasikan ulang, sementara lapisan visualnya tetap sepenuhnya dapat diedit.

Tombol di bawah ini menunjukkan pola tersebut dalam skala kecil. Ia tidak langsung terhubung ke primitive Radix, tetapi memperlihatkan dua elemen yang selalu menjadi sandaran setiap komponen shadcn/ui: Slot dari Radix, yang memungkinkan komponen dirender sebagai elemen bawaan yang berbeda tanpa node pembungkus tambahan, dan class-variance-authority, yang mengubah prop variant menjadi lookup bertipe dari string class Tailwind, bukan sekumpulan ekspresi kondisional.

Karena file ini berada di repositori kita, menambahkan variant baru, misalnya kombinasi destructive-outline yang tidak pernah dirilis desain aslinya, hanyalah sebuah edit lokal dan pull request, bukan feature request ke roadmap orang lain. Hal yang sama berlaku untuk menghapus dependensi yang tidak diinginkan: buang class-variance-authority dan tulis langsung string class jika tim lebih menyukai lebih sedikit bagian yang bergerak.

## Theming dengan CSS Variable dan Design Token

Komponen shadcn/ui tidak pernah menulis kode warna hex secara langsung. Setiap properti visual melewati sekumpulan kecil CSS variable, background, foreground, primary, primary foreground, dan beberapa lainnya, dinyatakan sebagai triple HSL agar bisa dirujuk di dalam fungsi warna Tailwind. Konfigurasi Tailwind kemudian memetakan class seperti bg-primary ke hsl(var(--primary)), yang berarti nama class yang sama menghasilkan piksel berbeda tergantung variable mana yang sedang aktif.

Lapisan indireksi tunggal itulah yang membuat rebranding tetap dapat dikelola dalam skala besar. Mengubah warna aksen sebuah produk berarti mengedit satu CSS variable sekali saja, bukan mencari kode hex yang tercecer di setiap file komponen. Mode gelap bekerja dengan cara yang sama: mengaktifkan class dark pada elemen html menukar set variable yang aktif, dan setiap komponen yang sudah membaca var(--background) otomatis mendapat palet baru tanpa perubahan kode di tingkat komponen.

> Lapisan token bersifat bersama, artinya kesalahan di lapisan itu juga tersebar bersama. Jika pasangan warna foreground-di-atas-background yang dipilih gagal memenuhi kontras WCAG, setiap komponen yang dibangun di atas token itu langsung mewarisi kegagalan tersebut. Periksa rasio kontras untuk set variable terang maupun gelap sebelum merilis perubahan token, bukan hanya set terang yang kebetulan sedang dilihat.

## Apa yang Sebenarnya Dikorbankan

Perbandingan yang jujur terhadap model dependensi tradisional bukanlah soal pakai library atau tidak, melainkan siapa yang menanggung biaya pemeliharaan dan ke mana bobot bundel itu mengalir. Tabel di bawah membandingkan tiga dimensi yang paling relevan dalam praktik.

| Dimensi | Dependensi tradisional | Copy-paste shadcn/ui |
| --- | --- | --- |
| Update | Kenaikan versi bisa mengubah perilaku atau gaya tanpa tim kita memilih untuk mengubah apa pun. | Tidak ada yang berubah sampai tim kita mengedit file itu sendiri, sesuai jadwal kita. |
| Ukuran bundel | Tetap membawa abstraksi library meski sudah tree-shaking, karena selalu ada sisa kode runtime yang menempel. | Hanya membawa komponen spesifik yang kita salin, tanpa logika variant yang tidak terpakai ikut terbundel. |
| Kedalaman kustomisasi | Terbatas pada apa yang diekspos oleh theme API atau prop override. | Terbatas hanya oleh seberapa jauh kita bersedia menulis ulang file sumbernya. |

Konsekuensinya, tanggung jawab pemeliharaan berpindah dari maintainer library ke tim kita sendiri. Perbaikan terkait keamanan pada Radix tetap datang lewat package manager kita, tetapi perbaikan pada kode wrapper milik shadcn/ui sendiri harus diterapkan secara manual, file demi file, di setiap komponen tempat kode itu disalin. Itu harga yang wajar untuk tim dengan disiplin code review dan pemilik design system yang jelas, sekaligus risiko nyata bagi proyek solo yang tidak memiliki keduanya.

## Alur Kerja Praktis untuk Mengembangkan Design System

Kepemilikan copy-paste hanya tetap terkelola bila tim memperlakukan kode yang disalin dengan disiplin yang sama seperti internal library lainnya. Beberapa kebiasaan berikut mencegahnya berubah menjadi drift yang tidak terkendali di puluhan komponen.

- Instal setiap primitive sekali lewat CLI ke components/ui dan perlakukan folder itu sebagai scaffolding hasil generate, model mental yang sama seperti output codegen yang boleh diedit tangan tapi tidak diformat ulang secara sembarangan.
- Bungkus primitive yang sudah terinstal dalam lapisan aplikasi terpisah untuk prop dan copy yang spesifik produk, sehingga kebutuhan satu halaman marketing tidak pernah memaksa perubahan pada primitive bersama yang juga dirender oleh semua layar lain.
- Simpan seluruh design token dalam satu file CSS dan dokumentasikan arti setiap nama variable, sehingga komponen baru selalu menyusun dari palet yang sama, bukan memunculkan corak warna yang sedikit berbeda dari warna yang sudah ada.
- Tambahkan poin checklist code review, atau aturan lint ringan, yang menandai edit langsung di dalam components/ui, sehingga perubahan pada primitive bersama selalu menjadi keputusan sengaja yang dibuat oleh seseorang yang bertanggung jawab atas design system, bukan efek samping tidak sengaja dari sebuah feature branch.

> Versi CLI shadcn/ui yang lebih baru mendukung registry ternamakan, memungkinkan sebuah tim mempublikasikan set komponen internalnya sendiri dengan cara yang sama seperti registry publik shadcn/ui bekerja. Ini menutup kesenjangan terbesar dari model copy-paste: alih-alih setiap repositori menyalin ulang dan lambat laun menyimpang dari tombol yang sama, sebuah perusahaan bisa memelihara satu registry internal dan setiap produk menarik darinya dengan perintah add yang sama.

## Kapan Kepemilikan Copy-Paste Tepat, dan Kapan Tidak

Arsitektur ini adalah trade-off yang nyata, bukan opsi yang selalu lebih baik, dan lebih cocok untuk beberapa situasi dibanding yang lain.

- Cocok: tim produk yang mengharapkan kustomisasi visual yang dalam dan berkelanjutan serta sudah memiliki pemilik design system yang mereview setiap perubahan komponen.
- Cocok: organisasi yang merilis beberapa aplikasi serupa yang bisa berbagi satu registry komponen internal daripada menduplikasi upaya di setiap repositori.
- Kurang cocok: tim yang membutuhkan perbaikan keamanan atau aksesibilitas menyebar ke setiap aplikasi konsumen begitu dirilis di hulu, karena library berbasis dependensi menghantarkannya lewat kenaikan versi sederhana, sementara kode yang disalin membutuhkan penerapan manual di setiap repositori.

Dalam praktiknya, kebanyakan tim produk berada di tengah-tengah: mereka menyalin primitive shadcn/ui demi kendali penuh atas lapisan visual, sambil tetap menarik Radix UI sendiri sebagai dependensi npm yang sesungguhnya untuk logika interaksi yang kritis bagi aksesibilitas di baliknya. Kepemilikan di tempat kustomisasi penting, dependensi di tempat kebenaran perilaku penting.

shadcn/ui tidak berusaha memenangkan kontes bundel paling kecil melawan library tradisional, ia mengusulkan bahwa sebuah design system seharusnya menjadi kode yang benar-benar dimiliki tim kita, bukan permukaan API yang dikendalikan orang lain. Radix menyediakan perilaku yang aksesibel, token Tailwind menyediakan tema, dan CLI menyediakan proses penyerahan satu kali. Apa yang dilakukan tim terhadap kode itu setelah penyerahan tersebutlah yang sebenarnya menentukan apakah arsitektur ini bisa berkembang.

## Sumber dan bacaan lebih lanjut:

- Dokumentasi shadcn/ui
- Dokumentasi Radix UI Primitives
- Tailwind CSS — Wikipedia

## Source links

- https://ui.shadcn.com/docs
- https://www.radix-ui.com/primitives/docs/overview/introduction
- https://en.wikipedia.org/wiki/Tailwind_CSS

## FAQ

### Apa yang membuat shadcn/ui berbeda dari component library biasa?

shadcn/ui tidak dirilis sebagai paket npm yang diimpor saat runtime. CLI-nya menyalin kode sumber komponen langsung ke repositori kita sendiri, sehingga tidak ada dependensi bersama yang perlu diversi, ditambal, atau dilawan lewat theme API. Kita akhirnya mengedit file biasa, bukan meng-override kotak hitam.

### Mengapa shadcn/ui menggunakan Radix UI di baliknya?

Radix UI Primitives menyediakan bagian tersulit dari sebuah komponen, yaitu logika interaksi yang tidak berstyle namun aksesibel mengikuti pola WAI-ARIA untuk navigasi keyboard, manajemen fokus, dan peran pembaca layar. shadcn/ui membungkus primitive tersebut dengan class Tailwind, sehingga perilaku aksesibilitas diwariskan dari Radix sementara gaya visualnya tetap bisa diedit penuh di kode yang sudah kita salin.

### Bagaimana theming bekerja di shadcn/ui jika tidak ada paket theme terpusat?

Theming berjalan lewat CSS variable, bukan objek theme JavaScript. Konfigurasi Tailwind memetakan utility class seperti bg-primary ke variable seperti hsl(var(--primary)), sehingga mengubah satu CSS variable memperbarui setiap komponen yang membacanya. Mode gelap bekerja dengan cara yang sama dengan menukar set variable aktif saat class dark diaktifkan.

### Bisakah saya menghapus dependensi seperti class-variance-authority dari komponen shadcn/ui?

Bisa, dan itu salah satu keuntungan utama memiliki kode sendiri. Karena komponen tersebut adalah file biasa di repositori kita, bukan ekspor dari sebuah paket, kita bisa membuang class-variance-authority, Radix Slot, atau bagian lain dan menggantinya dengan logika sendiri tanpa menunggu rilis dari upstream.

### Apakah model copy-paste shadcn/ui cocok untuk semua tim?

Tidak selalu. Model ini cocok untuk tim yang mengharapkan kustomisasi visual yang dalam dan memiliki pemilik design system yang mereview perubahan pada komponen bersama. Model ini kurang cocok untuk tim yang membutuhkan perbaikan keamanan atau aksesibilitas menyebar instan ke semua aplikasi konsumen, karena skenario itu lebih diuntungkan oleh dependensi yang dinaikkan versinya dibanding file hasil salinan yang ditambal manual.

---

- HTML version: https://www.matthewswong.com/id/blog/shadcn-ui-component-architecture/
- English: https://www.matthewswong.com/en/blog/shadcn-ui-component-architecture/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
