Shadcn UI · 철학, 설치, 구성
Shadcn UI? 는 버튼·다이얼로그·셀렉트 같은
컴포넌트? 를 설치하는 라이브러리가 아니라,
그 소스 코드를 내 프로젝트로 복사해 내가 소유하는 방식입니다.
여기서는 그 철학, 설치(init), 구성(components.json)을 차근차근 봅니다.
1. Shadcn UI란? — "복사해서 소유하는" 방식
전통적인 UI 라이브러리는 npm install 로 설치하면, 컴포넌트 코드가
node_modules 폴더 깊숙이 숨어 들어갑니다. 내가 직접 못 보고, 못 고치죠.
색이나 동작을 바꾸려면 라이브러리가 미리 열어둔 테마 옵션 범위 안에서만 가능합니다.
버전을 올리면 스타일이 통째로 바뀌기도 합니다(버전·스타일 종속).
Shadcn UI 는 정반대입니다. 컴포넌트를 추가하면 그 React?
+ TypeScript 코드 파일(.tsx)이 내 레포 안으로 복사됩니다.
그 순간부터 그건 라이브러리 코드가 아니라 내가 쓴 코드예요.
줄을 열어 클래스를 바꾸고, 동작을 더하고, 통째로 다시 써도 됩니다.
공식 문서의 표현 그대로 "이것은 컴포넌트 라이브러리가 아니라, 당신의 컴포넌트 라이브러리를 만드는 방법"입니다.
| 구분 | 전통 UI 라이브러리 (설치형) | Shadcn UI (복사형) |
|---|---|---|
| 가져오는 법 | npm install 후 import | CLI로 코드를 내 레포에 복사 |
| 코드 위치 | node_modules 안 (안 보임) | src/components/ui/ (내 코드) |
| 수정 자유 | 테마 옵션 범위 안에서만 | 코드를 직접 고쳐 무엇이든 |
| 버전 종속 | 업그레이드 시 스타일이 바뀔 수 있음 | 내 코드라 내가 멈춰둔 그대로 |
| 관리 책임 | 라이브러리가 관리 | 내가 관리 (자유의 대가) |
| 적합 | 빠르게 정해진 부품을 채울 때 | 디자인을 손에 쥐고 싶을 때 |
package.json 의존성 목록에서 "shadcn-ui" 같은 항목을 찾을 수 없습니다.
설치되는 건 도구(CLI)일 뿐이고, 컴포넌트는 그 도구가 내 파일로 복사해 줍니다.
버전(현재 3.7.0)은 라이브러리가 아니라 이 CLI 도구의 버전입니다.
2. 네 개의 기둥
복사된 컴포넌트 한 개를 열어 보면, 사실 아래 네 가지 도구가 손을 맞잡고 있습니다. 각자 역할이 또렷이 나뉘어 있어요.
| 기둥 | 역할 | 비유 |
|---|---|---|
| 헤드리스 프리미티브 (Radix UI / Base UI) | 동작과 접근성 (headless). 모양 없이 "여닫기·키보드·포커스·스크린리더" 같은 알맹이 동작만 담당. shadcn 기본은 Radix UI, 이 프로젝트는 후속인 Base UI(@base-ui/react) 사용 | 옷 없는 마네킹의 관절·뼈대 |
| Tailwind? | 스타일. 유틸리티 클래스(flex, px-4, rounded-md)로 겉모습을 입힘 | 마네킹에 입히는 옷 |
| cva | class-variance-authority. variant="outline" 같은 변형 규칙을 한곳에 깔끔히 정의 | 옷의 색·사이즈 카탈로그 |
| cn | clsx + tailwind-merge. 여러 클래스를 합치고 충돌을 정리(같은 종류는 뒤엣것이 이김) | 옷을 겹쳐 입을 때 코디 정리 |
cva 로 변형을 정의하는 모습 — 버튼의 variant 와 size 를 한곳에 모읍니다.
import { cva } from "class-variance-authority"
const buttonVariants = cva(
"inline-flex items-center rounded-md text-sm font-medium", // 공통
{
variants: {
variant: {
default: "bg-primary text-primary-foreground",
outline: "border border-input bg-background",
},
size: { sm: "h-8 px-3", lg: "h-10 px-6" },
},
defaultVariants: { variant: "default", size: "sm" },
}
)그리고 cn 은 "기본 클래스 + 내가 덧붙인 클래스"를 충돌 없이 합쳐 줍니다.
이 프로젝트의 실제 cn 정의는 이렇게 짧아요(src/lib/utils.ts).
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) // 합치고(clsx) → 충돌 정리(twMerge)
}
// 예: 같은 padding 이 충돌하면 뒤엣것이 이김
cn("px-2 py-1", "px-4") // → "py-1 px-4"3. 설치 / 초기화 — init 과 components.json
프로젝트에서 단 한 번, 초기화를 합니다. 이때 의존성을 깔고, 위에서 본 cn 유틸을 만들고,
CSS 변수를 설정하고, 무엇보다 components.json 이라는 설정 파일을 만듭니다.
# 프로젝트 한 번만 초기화 (CLI 3.7.0) npx shadcn@latest init # → components.json 생성 + cn 유틸 추가 + CSS 변수 설정
components.json 은 "앞으로 컴포넌트를 복사할 때 어떤 규칙으로 어디에 둘지"를 적어둔 메모지입니다.
이 프로젝트의 실제 설정값을 한 줄씩 보면서 의미를 익혀 봅시다.
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "base-vega",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/global.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}| 항목 | 이 프로젝트 값 | 의미 (쉽게) |
|---|---|---|
$schema | ui.shadcn.com/schema.json | 이 파일의 형식 검사용 주소. 오타나면 에디터가 알려줌 |
style | base-vega | 컴포넌트 스타일. base- 는 헤드리스 기반이 Base UI임을, vega 는 디자인 톤을 뜻함(전통 기본은 radix-=Radix UI). 초기화 후엔 바꾸기 어려움 |
rsc | false | React Server Components 안 씀. 그래서 "use client" 자동 추가 안 함 |
tsx | true | TypeScript(.tsx)로 복사. false면 .jsx |
tailwind.config | (빈 값) | 비워둠 = Tailwind v4 방식(설정을 CSS에서 함) |
tailwind.css | src/app/global.css | Tailwind를 불러오는 CSS 파일 위치 |
tailwind.baseColor | neutral | 기본 색 팔레트(무채색 계열). 초기화 후엔 바꾸기 어려움 |
tailwind.cssVariables | true | 색을 CSS 변수(예: --primary)로 관리 → 다크모드·테마 쉬움 |
tailwind.prefix | (빈 값) | 클래스 앞에 붙일 접두사 없음(예: tw- 안 씀) |
iconLibrary | lucide | 아이콘은 lucide 사용 → lucide-react 패키지 |
aliases.components | @/components | 일반 컴포넌트 import 경로 |
aliases.ui | @/components/ui | 복사된 shadcn 컴포넌트가 들어갈 폴더 |
aliases.utils | @/lib/utils | cn 같은 유틸 위치 |
aliases.lib | @/lib | 공용 라이브러리 폴더 |
aliases.hooks | @/hooks | 커스텀 훅 폴더 |
@/ 는 "src 폴더부터"를 뜻하는 별명입니다. CLI는 이 별명들을 보고
복사한 파일을 어디에 둘지, 또 import 문을 어떻게 쓸지 정합니다.
그래서 ../../components/ui/button 대신 늘 @/components/ui/button 처럼 깔끔하게 쓸 수 있어요.
4. 컴포넌트 추가 — add
초기화가 끝나면, 필요한 컴포넌트를 하나씩 골라 추가합니다. 그때마다 코드 파일이 내 폴더로 복사됩니다.
# 버튼 컴포넌트를 내 코드로 복사 npx shadcn@latest add button # → src/components/ui/button.tsx 파일이 생김 (내 코드!) # (aliases.ui = "@/components/ui" 설정을 따라 이 위치로 들어감)
이제 그 버튼을 import 해서 씁니다. variant 는 위에서 본 cva 가 정의한 변형이에요.
import { Button } from "@/components/ui/button"
function Toolbar() {
return (
<div className="flex gap-2">
<Button>저장</Button>
<Button variant="outline">취소</Button>
</div>
)
}마음에 안 드는 부분이 있으면? button.tsx 를 열어 직접 고치면 됩니다 — 그게 곧 내 디자인이 됩니다.
5. 아이콘 — lucide-react
설정의 iconLibrary: "lucide" 에 따라, 아이콘은 lucide-react 에서 컴포넌트처럼 가져다 씁니다.
각 아이콘이 하나의 컴포넌트? 라서, 크기·색을 props 와 클래스로 조절해요.
import { Search, Plus } from "lucide-react"
import { Button } from "@/components/ui/button"
function SearchBar() {
return (
<Button>
<Search className="size-4" /> {/* 아이콘도 컴포넌트 */}
검색
</Button>
)
}6. 폴더 구조 — 복사본은 어디에?
설정의 aliases 대로, 복사된 컴포넌트와 유틸은 정해진 자리에 모입니다.
src/ ├─ components/ │ └─ ui/ ← aliases.ui (@/components/ui) │ ├─ button.tsx 복사된 shadcn 컴포넌트들 │ ├─ dialog.tsx (모두 내 코드, 수정 가능) │ └─ select.tsx ├─ lib/ │ └─ utils.ts ← aliases.utils (@/lib/utils): cn 정의 └─ app/ └─ global.css ← tailwind.css: Tailwind 불러옴
핵심만 기억하면 됩니다. 컴포넌트는 @/components/ui 에,
이들을 받쳐주는 cn 은 @/lib/utils 에 있습니다.
7. 버튼 모양 미리보기 (라이브)
복사되는 버튼이 대략 어떤 모습인지, Tailwind? 클래스로 흉내 낸 미리보기입니다. (실제 컴포넌트는 cva·cn 로 더 정교하게 만들어집니다.)
한눈에 — 구성 요약
| 요소 | 역할 한 줄 | 이 프로젝트 |
|---|---|---|
| 방식 | 코드를 복사해 내가 소유 | CLI 3.7.0 |
| 헤드리스 프리미티브 | 동작·접근성(headless) | Base UI (@base-ui/react, base-vega) |
| Tailwind | 스타일(유틸 클래스) | global.css |
| cva | 변형(variant) 정의 | button 등에서 |
| cn | 클래스 병합·충돌 정리 | @/lib/utils |
| 설정 | 복사 규칙 | components.json (base-vega/neutral) |
| 아이콘 | 아이콘 컴포넌트 | lucide-react |
| 위치 | 복사본 폴더 | @/components/ui |
이 프로젝트의 실제 components.json 은 style base-vega, baseColor neutral,
아이콘 lucide, tsx: true, rsc: false, cssVariables true 로 설정돼 있고,
복사된 컴포넌트는 @/components/ui, cn 은 @/lib/utils 에 있습니다.
프론트엔드 환경 전반은 프론트엔드 환경(env-frontend) 에서 함께 보세요.
다음 단계
- 실제 컴포넌트들 둘러보기 → Shadcn 컴포넌트
- 바탕이 되는 스타일 방식 → Tailwind CSS
- 프론트엔드 환경 전반 → env-frontend