본문으로 건너뛰기
5분 AI.
REPO DEEP DIVE · 2026-07-29 · SHADCN-UI/UI · npm 패키지가 아니라 "복붙되는 레지스트리"

shadcn-ui/ui 딥다이브
— "설치하지 않는 컴포넌트"가 만드는 AI 시대의 UI 표준

CLI·컴포넌트 레지스트리

빠른 판단

무엇
React·Tailwind 컴포넌트 소스를 프로젝트로 가져와 직접 수정하게 하는 CLI와 레지스트리
이럴 때
기본 UI와 접근성 구현을 가져오되 컴포넌트 코드를 사내 디자인 규칙에 맞춰 직접 관리할 때
shadcn/ui는 겉보기엔 React용 UI 컴포넌트 모음이지만, 결정적으로 npm install로 설치하는 라이브러리가 아니다. CLI(npx shadcn add button)로 부르면 Button 컴포넌트의 실제 소스 코드 파일이 내 프로젝트 폴더 안에 그대로 복사되고, 그 순간부터는 100% 내 코드다. 저장소가 관리하는 건 완제품 라이브러리가 아니라 "레지스트리" — 수백 개 컴포넌트의 소스와 메타데이터, 그리고 그걸 프로젝트 사정에 맞게 고쳐 붙여넣는 CLI 엔진이다.

2026년 현재는 한 발 더 나아가 Radix UI · Base UI · React Aria 세 가지 헤드리스 프리미티브 중 하나를 고르고, 8가지 스타일 프리셋을 base62 코드 하나로 압축해 공유하는 --preset 시스템, 그리고 Claude Code용 공식 스킬 + MCP 서버까지 갖춘 "AI 에이전트가 직접 다루는 디자인 시스템"으로 진화했다.

(저장소 shadcn-ui/ui · 제작 shadcn · 라이선스 MIT · CLI 패키지명은 "shadcn" v4.16.0 · pnpm 워크스페이스 모노레포(패키지 4개+앱 1개) · 등록 컴포넌트 411개(classic 레지스트리 기준) · Next.js 16.2.7 / React 19.2.3 / Tailwind CSS v4.3 · 최신 커밋 2026-07-29, 해시 29ea362)
목차
  1. 한 줄 정체
  2. 왜 지금 뜨는가
  3. 무엇을 해결하나
  4. 전체 구조
  5. 핵심 설계·동작 원리
  6. 기술 스택·의존성
  7. 설치·요구사항
  8. 실제 사용 흐름
  9. 함정·오해 방지
  10. 대안 비교
  11. 정리 · 누가 쓰면 좋은가
  12. 직접 해볼 수 있는 실습 과제
  13. 관련 기술 심화 학습 로드맵
  14. 핵심 키워드 사전

1한 줄 정체

"컴포넌트 라이브러리"가 아니라 "코드를 나눠주는 CLI + 레지스트리"

shadcn/ui는 npm 패키지가 아니다. 이 문장이 이 저장소를 이해하는 가장 중요한 출발점이다. Material UI(MUI)나 Chakra UI 같은 전통적인 컴포넌트 라이브러리는 npm install로 검은 상자(black box) 같은 완제품 패키지를 프로젝트에 얹고, 그 패키지의 API를 익혀 사용한다. 반면 shadcn/ui는 CLI가 컴포넌트의 실제 소스 코드 파일을 통째로 복사해 내 프로젝트의 components/ui 폴더에 붙여넣는다. 설치가 끝나는 순간 그 파일은 라이브러리의 일부가 아니라 내가 작성한 것처럼 자유롭게 고칠 수 있는 내 코드가 된다.

루트 README.md는 이 철학을 "Open Source. Open Code."라는 슬로건으로 못 박는다. 실제로 저장소 안에는 두 가지 큰 축이 공존한다. 하나는 레지스트리 — 버튼·다이얼로그·폼 같은 UI 조각과 대시보드·로그인 화면 같은 블록(block)의 소스 코드 및 메타데이터를 모아둔 JSON+파일 더미(apps/v4/registry, 컴파일 결과물 registry.json에 411개 항목). 다른 하나는 CLI(npm 패키지명 shadcn) — 그 레지스트리에서 필요한 항목을 골라, 내 프로젝트의 폴더 구조·별칭(alias)·Tailwind 설정에 맞게 코드를 변형(transform)해 옮겨 심는 엔진이다.

한 줄 비유

가구를 "완제품으로 배송"하는 대신 "치수에 맞춰 재단해 내 손에 쥐여주는" 목공소

보통의 UI 라이브러리는 이케아 완제품 가구와 같다. 정해진 규격대로 조립만 하면 되지만, 내 집 벽 색깔에 맞춰 페인트를 새로 칠하거나 다리 길이를 바꾸는 건 어렵거나 아예 불가능하다.

shadcn/ui는 목공소에 가서 "이 치수, 이 나무 종류로 의자 하나 만들어 주세요"라고 주문하는 것에 가깝다. CLI가 내 프로젝트의 치수(폴더 구조·별칭·Tailwind 설정)를 재고, 그에 맞게 소스를 재단해서 완성된 원목 그대로 내 손에 넘긴다. 그 순간부터 사포질도, 페인트칠도 전부 내 몫이자 내 자유다.

2왜 지금 뜨는가

AI 코드 생성 시대의 "정답 소스" · 헤드리스 프리미티브 다변화 · 프리셋 공유 문화

shadcn/ui는 이미 여러 해 전부터 React 생태계의 사실상 표준으로 자리 잡았지만, 2026년 시점에 이 저장소를 다시 들여다볼 이유는 따로 있다. 코드 자체가 계속 구조적으로 진화하고 있고, 그 방향이 정확히 "AI 에이전트가 UI 코드를 직접 다루는 시대"를 정조준하고 있기 때문이다.

① Claude Code가 직접 읽는 공식 "스킬"이 저장소 안에 산다

저장소 안의 skills/shadcn/SKILL.md는 Claude Code 같은 코딩 에이전트가 이 프로젝트를 만질 때 지켜야 할 규칙을 담은 공식 에이전트 스킬 문서다. "기존 컴포넌트를 먼저 검색해라", "className은 레이아웃에만 써라, 색상은 시맨틱 토큰으로", "Dialog는 반드시 Title이 있어야 한다" 같은 실전 규칙이 Incorrect/Correct 코드 쌍과 함께 정리돼 있다. 즉 이 저장소 자체가 "AI가 UI를 짤 때 참고하는 교과서" 역할을 자처한다.

② 세 가지 헤드리스 프리미티브로 "Radix 종속"을 끊다

오랫동안 shadcn/ui는 사실상 Radix UI 위의 스타일링 레이어였다. 그런데 현재 저장소의 apps/v4/registry/bases/ 아래에는 radix · base · aria 세 개의 병렬 디렉터리가 있다. 같은 Button이라도 Radix UI 기반, Base UI(MUI 팀의 새 헤드리스 라이브러리) 기반, React Aria(Adobe) 기반 세 가지 소스가 따로 존재한다. 심지어 skills/migrate-radix-to-base라는 전용 마이그레이션 스킬까지 갖췄다. 특정 헤드리스 벤더에 락인(lock-in)되지 않겠다는 의지가 코드로 드러난다.

③ 프리셋 코드 한 줄로 "디자인 시스템 전체"를 공유한다

packages/shadcn/src/preset/preset.ts는 베이스(radix/base/aria) · 스타일(nova, vega, maia, lyra, mira, luma, sera, rhea 8종) · 기본 색상 · 테마 색상을 비트 단위로 압축해 하나의 base62 문자열로 인코딩한다. npx shadcn init --preset <코드> 한 줄이면 팀 전체가 똑같은 디자인 시스템 조합으로 시작할 수 있다. "우리 팀 스타일은 이 코드입니다"라고 URL처럼 공유하는 문화가 가능해진 것이다.

경쟁 지형에서의 위치
"라이브러리를 쓰는" 시대에서 "코드를 받는" 시대로

MUI·Chakra UI·Mantine 같은 전통 라이브러리는 여전히 강력하지만 커스터마이징이 결국 그 라이브러리의 테마 API 안에 갇힌다. shadcn/ui는 "라이브러리 유지보수 없는 오픈소스"라는 독특한 자리를 차지했고, v0·Claude Code 같은 AI 코드 생성 도구들이 사실상 shadcn/ui 코드를 표준 출력 형식처럼 채택하면서 그 입지가 더 굳어졌다.

3무엇을 해결하나

"라이브러리의 검은 상자" 대 "밑바닥부터 직접 구현" 사이의 오래된 딜레마

React로 UI를 만들 때 개발자는 늘 두 가지 나쁜 선택지 사이에서 골랐다. 하나는 완제품 라이브러리를 설치하는 것 — 빠르지만, 디자인을 깊이 바꾸려면 라이브러리가 허용하는 테마 옵션 안에서만 움직여야 하고, 접근성(accessibility) 버그를 만나도 내가 고칠 수 없어 이슈 트래커에 줄을 서야 한다. 다른 하나는 Radix UI 같은 헤드리스 프리미티브 위에 처음부터 직접 스타일링하는 것 — 자유롭지만, 버튼 하나 다이얼로그 하나에도 접근성·포커스 트랩·애니메이션을 처음부터 다 챙겨야 해서 느리다.

shadcn/ui는 이 둘을 "이미 잘 만들어진 답을 받아서, 그 답을 내 것처럼 고친다"는 제3의 길로 풀었다. 컴포넌트는 이미 Radix UI/Base UI/React Aria라는 검증된 접근성 엔진 위에서 동작하도록 완성돼 있고, 스타일은 Tailwind 유틸리티 클래스로 눈에 훤히 보이게 짜여 있다. 개발자는 "접근성이 되는 컴포넌트를 밑바닥부터 만드는 시간"을 아끼면서도, "그 컴포넌트의 모든 코드 한 줄까지 내 마음대로 바꿀 자유"를 동시에 얻는다.

같은 문제의식이 CLI의 변형기(transformer) 파이프라인에도 이어진다. 컴포넌트를 그냥 복사만 하면 프로젝트마다 다른 import 별칭(@/components vs ~/ui 등), CSS 변수 사용 여부, Tailwind 접두사(prefix) 설정 때문에 코드가 그대로 동작하지 않는다. CLI는 이 지점을 ts-morph 기반 AST 변형으로 자동 보정해, "복사"가 아니라 "내 프로젝트에 맞게 재조립된 이식"이 되게 한다.

용어
헤드리스(headless) 컴포넌트
시각적 스타일은 전혀 없이 키보드 접근성·포커스 관리·ARIA 속성 같은 "동작"만 구현해 둔 컴포넌트. Radix UI·Base UI·React Aria가 대표적이다. shadcn/ui는 이 위에 Tailwind로 스타일만 입혀 완성한다.
용어
레지스트리(registry)
컴포넌트 이름·타입·의존성·파일 경로를 담은 JSON 목록과 실제 소스 파일 더미. CLI는 이 레지스트리를 조회해 어떤 파일을 어디로 복사할지 결정한다. shadcn 공식 레지스트리 외에 누구나 자기만의 레지스트리를 만들어 @내레지스트리 이름으로 등록할 수 있다.

4전체 구조

pnpm 워크스페이스 모노레포 — apps/v4(문서+레지스트리 사이트) · packages/shadcn(CLI) · packages/react · packages/helpers

저장소는 pnpm-workspace.yaml로 관리되는 모노레포이며, 빌드는 Turborepo, 릴리스는 Changesets가 맡는다. 최상위는 apps/와 packages/ 둘로 나뉘는데, 역할 분담이 뚜렷하다.

┌────────────────────────────────────────────────────────────────┐ │ SHADCN-UI/UI 저장소 구조 (요약) │ └────────────────────────────────────────────────────────────────┘ apps/v4/ # Next.js 16 — ui.shadcn.com 문서 + 레지스트리 서버 ├─ registry/ │ ├─ new-york-v4/ # 클래식 스타일 (인라인 Tailwind 클래스, 레거시 유지) │ └─ bases/{radix,base,aria}/ # 신규: 헤드리스 베이스 3종 × 프리셋 스타일 시스템 ├─ registry.json # 컴파일된 레지스트리 색인 — 411개 항목 └─ public/r/*.json # CLI가 실제로 fetch하는 배포용 JSON packages/ ├─ shadcn/ # ★ CLI 본체 — npm 패키지명 "shadcn" v4.16.0 │ └─ src/ │ ├─ commands/ # add·init·build·diff·migrate·mcp·search… │ ├─ registry/ # 레지스트리 스키마·페처·리졸버·검증기 │ ├─ utils/transformers/ # ts-morph AST 변형기 15종 │ ├─ preset/ # --preset 코드 인코딩/디코딩 │ └─ mcp/ # Model Context Protocol 서버 ├─ react/ # @shadcn/react — 순수 헤드리스 컴포넌트(스타일 없음) ├─ helpers/ # @shadcn/helpers — AI 채팅 UI용 소품 헬퍼 └─ tests/ # 워크스페이스 통합 테스트 픽스처 skills/ # Claude Code용 공식 에이전트 스킬(shadcn, migrate-radix-to-base) templates/ # create-next-app 등 프레임워크별 스타터 템플릿

이 배치가 말해주는 것은 명확하다. apps/v4는 "레지스트리 데이터의 원천이자 문서 사이트"이고, packages/shadcn은 "그 데이터를 소비해 사용자 프로젝트에 이식하는 CLI"다. 이 두 축을 분리해 뒀기 때문에, 레지스트리 콘텐츠(컴포넌트 종류·스타일)와 CLI 엔진(변형·설치 로직)이 독립적으로 버전업된다 — 실제로 CLI는 v4.16.0인 반면 레지스트리 콘텐츠는 매일같이 커밋되며 계속 늘어난다.

5핵심 설계·동작 원리

CLI add 파이프라인 · AST 변형기 15종 · 프리셋 인코딩 · 3중 헤드리스 베이스 · MCP 서버

설계 원리 ① — "add" 한 줄 뒤에 숨은 5단계 파이프라인

packages/shadcn/src/commands/add.ts는 npx shadcn add button 같은 명령이 들어왔을 때 실제로 일어나는 일을 조율하는 진입점이다. 핵심 흐름은 다음과 같다.

1. 프로젝트에 components.json이 있는지 확인 → 없으면 promptForBase() + promptForPreset()로 대화형 초기화(runInit) 실행 2. getRegistryItems([componentName])로 레지스트리에서 항목 메타데이터 조회 → registry:style / registry:theme 타입이면 "기존 CSS 변수를 덮어씁니다" 확인창 3. addComponents() → resolveRegistryTree() → 의존 관계(예: alert-dialog가 button을 필요로 함)를 재귀적으로 풀어 설치해야 할 파일 전체 목록(tree)을 계산 4. updateFiles() + updateCss() + updateDependencies() + updateTailwindConfig() + updateFonts() + updateEnvVars() → 실제 파일 쓰기, package.json 의존성 추가, globals.css의 CSS 변수/유틸리티 병합, 폰트·환경변수 주입까지 한 번에 5. --dry-run / --diff / --view 옵션이면 4번 대신 dryRunComponents()로 "무엇이 바뀔지"만 미리 보여주고 종료

주목할 지점은 4단계가 단순 파일 복사가 아니라는 것이다. 컴포넌트 파일 하나를 옮기기 전에, CLI는 그 파일을 ts-morph로 AST(추상 구문 트리)까지 파싱해 15종의 변형기(transformer)를 순서대로 통과시킨다.

설계 원리 ② — AST 변형기: "복사"가 아니라 "재조립"

packages/shadcn/src/utils/transformers/ 폴더에는 transform-import.ts, transform-css-vars.ts, transform-tw-prefix.ts, transform-rsc.ts, transform-icons.ts, transform-jsx.ts 등 15개 파일이 있다. 각각이 컴포넌트 소스 코드의 특정 측면 하나씩을 프로젝트 사정에 맞춰 고쳐 쓴다.

변형기 예시 ①

transform-import.ts — import 경로를 프로젝트 별칭으로 재작성

레지스트리 소스는 항상 @/registry/... 같은 공통 경로로 쓰여 있다. updateImportAliases() 함수는 이 경로를 components.json의 aliases 설정(예: @/components/ui, ~/lib/utils)에 맞춰 정규식으로 치환한다. 예를 들어 @/registry/(.+)/ui 패턴을 만나면 config.aliases.ui 값으로, @/registry/(.+)/lib/utils는 config.aliases.utils 값으로 바꾼다. 워크스페이스 모노레포처럼 유틸 별칭이 @myorg/ui/lib/utils처럼 복잡한 경우까지 getWorkspaceAliasFromUtilsAlias()가 별도로 처리한다.

변형기 예시 ②

transform-css-vars.ts — CSS 변수를 안 쓰는 프로젝트를 위한 색상 치환

컴포넌트 기본 소스는 bg-primary처럼 CSS 변수 기반 시맨틱 클래스를 쓴다. 그런데 components.json의 tailwind.cssVariables가 false인 프로젝트(CSS 변수 대신 고정 팔레트를 쓰는 경우)라면, applyColorMapping() 함수가 splitClassName()으로 변형자(variant)·클래스명·투명도(alpha)를 분해한 뒤 bg-primary → bg-zinc-900 식으로 라이트/다크 모드 클래스 쌍을 직접 만들어 넣는다. "네 프로젝트가 CSS 변수를 안 쓴다면, 대신 구체적인 색상값을 박아주겠다"는 안전망이다.

비유

변형기 파이프라인은 이삿짐센터의 "치수 재조립 서비스"와 같다. 원래 집(레지스트리)의 붙박이 옷장(컴포넌트)을 통째로 뽑아 새 집(내 프로젝트)에 그냥 놓으면 문틀 크기가 안 맞는다. 이삿짐센터는 옷장을 분해(AST 파싱)해서, 새 집의 실측(components.json의 별칭·설정)에 맞게 못 위치와 나사 길이(import 경로·클래스명)를 재조정한 뒤 다시 조립해 넣는다.

설계 원리 ③ — "베이스 3종 × 스타일 8종"을 하나의 정수로 압축하는 프리셋

packages/shadcn/src/preset/preset.ts는 매우 실용적인 엔지니어링 트릭을 보여준다. 베이스(PRESET_BASES = ["radix","base","aria"]) · 스타일(PRESET_STYLES 8종: nova·vega·maia·lyra·mira·luma·sera·rhea) · 기본 색상 8종 · 테마 색상을 각각 배열의 인덱스로 표현하고, 이 인덱스들을 비트 단위로 이어붙여 JS의 안전 정수 범위(53비트) 안에서 하나의 숫자로 합친 뒤 base62 문자열로 인코딩한다. 파일 맨 위 주석에 못박힌 규칙이 인상적이다: "기존 값 배열은 절대 재정렬하지 말고 뒤에만 추가하라" — 한 번 배포된 프리셋 코드가 나중 버전에서도 똑같이 해석돼야 하기 때문에, 하위 호환을 위한 규율을 코드 주석으로 강제하고 있다.

이 덕분에 npx shadcn init --preset <62진수 코드> 한 줄로 "Base UI 베이스 + vega 스타일 + zinc 기본색 + amber 테마"라는 조합 전체를 순식간에 복원할 수 있다. parsePresetStyle()은 반대로 "radix-nova" 같은 문자열에서 베이스와 스타일 이름을 다시 분리해낸다.

설계 원리 ④ — 헤드리스 베이스 3종: 같은 API, 다른 엔진

apps/v4/registry/bases/radix/ui/button.tsx와 bases/base/ui/button.tsx, bases/aria/ui/button.tsx를 나란히 열어 보면, Radix 버전은 import { Slot } from "radix-ui"를, Base 버전은 Base UI의 프리미티브를, Aria 버전은 React Aria의 훅을 각각 가져다 쓴다. 겉으로 드러나는 Button의 props와 사용법은 거의 동일하게 맞춰져 있지만, 내부에서 접근성·포커스·이벤트를 처리하는 엔진 자체가 다르다. 그래서 프로젝트가 이미 Base UI 생태계를 쓰고 있다면 Radix 의존성을 하나도 늘리지 않고 shadcn 컴포넌트를 쓸 수 있다.

설계 원리 ⑤ — 신규 프리셋 스타일은 클래스가 아니라 "이름"만 컴포넌트에 남긴다

더 미묘하지만 중요한 변화가 하나 더 있다. 클래식 new-york-v4 스타일은 지금도 bg-primary text-primary-foreground hover:bg-primary/90처럼 Tailwind 유틸리티 클래스를 컴포넌트 파일 안에 그대로 박아 둔다("보이는 그대로가 전부"라는 원조 철학). 반면 새 프리셋 베이스(radix/base/aria)의 Button은 "cn-button cn-button-variant-default"처럼 의미만 담은 클래스 이름을 쓰고, 실제 Tailwind 유틸리티는 apps/v4/registry/styles/style-nova.css 같은 별도 스타일시트 안에 @apply로 정의돼 있다. 스타일 이름(nova·vega 등)을 갈아 끼우면 컴포넌트 코드를 한 글자도 안 건드리고 룩앤필 전체가 바뀌는 구조다.

설계 원리 ⑥ — MCP 서버: 에이전트가 레지스트리를 직접 검색하게 하기

packages/shadcn/src/mcp/index.ts(576줄)는 MCP(Model Context Protocol) 서버를 구현한다. get_project_registries(components.json에 등록된 레지스트리 목록 조회), list_items_in_registries, search_items_in_registries(퍼지 검색), get_item_examples_from_registries 같은 도구를 노출해, Claude 같은 에이전트가 "이 프로젝트에 데이터 테이블 컴포넌트가 있나?"를 코드를 뒤지지 않고 구조화된 도구 호출로 바로 물어볼 수 있게 한다. 실제 설치는 여전히 npxShadcn() 헬퍼가 내부적으로 npx shadcn add를 실행해 처리한다 — MCP는 "탐색"을, CLI는 "실행"을 맡는 분업이다.

용어
AST (Abstract Syntax Tree, 추상 구문 트리)
소스 코드를 텍스트가 아니라 "이건 import문, 이건 함수 선언" 같은 구조화된 나무 모양 데이터로 표현한 것. ts-morph 같은 라이브러리는 이 트리를 직접 조작해, 문자열 치환보다 훨씬 안전하게 코드를 고쳐 쓸 수 있게 해준다.
용어
MCP (Model Context Protocol)
AI 에이전트가 외부 도구·데이터에 표준화된 방식으로 접근하게 해주는 프로토콜. shadcn/ui는 이를 통해 "레지스트리에 어떤 컴포넌트가 있는지" 같은 정보를 에이전트가 직접 조회하게 한다.

6기술 스택 · 의존성

CLI 코어(ts-morph·commander·zod) · 문서/레지스트리 앱(Next.js 16+React 19+Tailwind v4) · 버전의 진실원
영역구성
CLI 코어ts-morph 26(AST 변형) · commander 14(명령어 파싱) · zod 3 + zod-to-json-schema(스키마 검증·MCP 툴 스키마 변환) · execa 9(하위 프로세스) · fast-glob·fs-extra(파일 처리) · fuzzysort(검색) · diff 8(--diff 명령) · cosmiconfig(components.json 탐색)
MCP@modelcontextprotocol/sdk 1.26
스타일링(컴포넌트 소스)Tailwind CSS · class-variance-authority(cva, variant 조합 관리) · tailwind-merge 3(클래스 충돌 정리) · radix-ui(통합 패키지) · Base UI · React Aria
문서·레지스트리 앱(apps/v4)Next.js 16.2.7 · React 19.2.3 · Tailwind CSS v4.3 · TypeScript 5
빌드·릴리스pnpm 10.33.4 워크스페이스 · Turborepo(태스크 오케스트레이션) · Changesets(버저닝·체인지로그) · tsup(패키지 번들링)
테스트Vitest 워크스페이스 구성 — 저장소 전체에 .test.ts 111개, .test.tsx 3개

버전의 진실원(source of truth)은 하나가 아니라 세 갈래라는 점이 헷갈리기 쉽다. CLI 자체(packages/shadcn/package.json)는 npm에 "shadcn"이라는 이름으로 배포되며 버전은 4.16.0이다. 헤드리스 전용 패키지 @shadcn/react는 별도로 0.2.1, AI 채팅 UI 헬퍼 @shadcn/helpers는 0.1.0이다. 반면 컴포넌트 그 자체(레지스트리 콘텐츠)는 npm 버전이 없다 — 복사된 순간의 스냅샷이 곧 버전이고, 최신화는 shadcn diff로 수동 비교하는 방식이다.

용어
cva (class-variance-authority)
"variant='outline' size='sm'"처럼 이름 붙은 옵션 조합에 따라 Tailwind 클래스 문자열을 자동으로 골라주는 작은 라이브러리. shadcn 컴포넌트 대부분이 buttonVariants = cva(...) 형태로 스타일 분기를 정의한다.

7설치 · 요구사항

Node ≥20.18.1 · 기존 React+Tailwind 프로젝트 필요 · components.json이 모든 설정의 출발점
항목요구사항
Node.jspackages/shadcn/package.json의 engines 기준 ≥20.18.1
초기화npx shadcn@latest init (또는 pnpm dlx/bunx --bun) — components.json 생성, base color CSS 변수를 globals.css에 주입
프리셋 초기화npx shadcn@latest init --preset <코드> — 베이스·스타일·색상 조합을 한 번에 복원
컴포넌트 추가npx shadcn@latest add button dialog — 여러 개를 한 번에 지정 가능, -a/--all로 전체 설치
필수 전제이미 React + Tailwind CSS가 세팅된 프로젝트(Next.js·Vite·Remix 등). CLI는 프레임워크를 스캐폴딩하는 도구가 아니라, 기존 프로젝트에 코드를 이식하는 도구다(단, 빈 폴더에서 실행하면 대화형으로 새 프로젝트 생성도 도와준다).
패키지 매니저 무관npm·pnpm·yarn·bun 어디서든 npx/dlx/bunx로 실행 가능. CLI 자체가 특정 패키지 매니저에 종속되지 않는다.
주의
"설치"의 의미가 두 겹이다

· 컴포넌트를 "설치"한다는 말은 npm 패키지를 추가한다는 뜻이 아니라 소스 파일을 복사한다는 뜻이다. 다만 그 컴포넌트가 필요로 하는 진짜 npm 의존성(예: @radix-ui/react-slot)은 CLI가 package.json에 실제로 추가해 준다 — 이건 여전히 npm install 대상이다.

· components.json의 aliases 설정(어디에 ui/components/hooks/lib를 둘지)을 CLI가 매번 참조해 import 경로를 재작성한다. 이 파일을 지우거나 손으로 잘못 고치면, 다음 add 실행 때 엉뚱한 경로로 파일이 떨어질 수 있다.

8실제 사용 흐름

init → add → (사람이 직접 편집) · 에이전트라면 MCP 검색 → CLI 설치
흐름 1

새 프로젝트에 처음 세팅하기

npx shadcn@latest init을 실행하면 대화형 프롬프트로 베이스(Radix/Base UI/React Aria)와 스타일을 고르고, components.json이 생성되며 globals.css에 색상 CSS 변수와 @import "shadcn/tailwind.css" 한 줄이 추가된다. 이 시점부터 프로젝트는 "shadcn 준비 완료" 상태가 된다.

흐름 2

컴포넌트 하나 추가하기 — npx shadcn add button

CLI가 레지스트리에서 button 항목을 찾고, 의존 트리(예: alert-dialog라면 그 안에 쓰이는 button까지)를 재귀적으로 계산한 뒤, 각 파일을 ts-morph 변형 파이프라인에 통과시켜 src/components/ui/button.tsx(또는 별칭 경로) 위치에 실제로 써 넣는다. 필요한 npm 의존성은 package.json에 추가되고(사용자가 직접 pnpm install 실행), Tailwind 설정·CSS 변수·폰트도 함께 갱신된다.

흐름 3

복사된 코드를 "내 것처럼" 고치기

설치가 끝나면 button.tsx 안의 buttonVariants(cva 정의)를 열어 variant: "brand" 같은 새 옵션을 직접 추가해도 아무 문제가 없다. 이건 "커스터마이징 API를 배우는" 게 아니라 "그냥 내 파일을 고치는" 행위다. 나중에 shadcn 쪽에서 같은 컴포넌트를 업데이트해도 자동으로 반영되지 않는다 — 필요하면 npx shadcn diff button으로 수동 비교해야 한다.

흐름 4 (에이전트 경로)

Claude Code 같은 에이전트가 컴포넌트를 고를 때

components.json이 있는 프로젝트에서 에이전트는 MCP 도구 search_items_in_registries로 "테이블에 정렬 기능이 있는 컴포넌트"를 퍼지 검색하고, get_item_examples_from_registries로 사용 예시를 확인한 뒤, 실제 설치는 npxShadcn()을 통해 평범한 npx shadcn add data-table 셸 명령으로 수행한다. 사람이 터미널에 치는 것과 최종 경로는 같다.

9함정 · 오해 방지

"복붙이니 버전 관리가 필요 없다"부터 "전부 Radix다"까지, 실제 코드와 다른 통념들

함정 ① "npm install이 아니니 버전 관리를 신경 안 써도 된다"

절반만 맞다. 컴포넌트 코드 자체는 복사된 순간의 스냅샷이라 자동 업데이트가 없는 게 맞지만, 그 컴포넌트가 쓰는 진짜 npm 패키지(@radix-ui/react-slot, class-variance-authority 등)는 package.json에 그대로 등록돼 일반적인 npm 의존성처럼 버전이 올라간다. 그리고 shadcn 쪽 원본 코드가 나중에 버그를 고쳐도 내 프로젝트엔 자동 반영되지 않으므로, shadcn diff로 주기적으로 비교하는 습관이 필요하다.

함정 ② "shadcn/ui는 Radix UI 래퍼다"

한때는 정확한 설명이었지만 지금은 절반만 맞다. apps/v4/registry/bases/에 radix·base·aria 세 베이스가 나란히 존재하고, 심지어 Radix에서 Base UI로 옮겨가도록 돕는 skills/migrate-radix-to-base 공식 마이그레이션 스킬까지 있다. "전부 Radix 위에서 돈다"는 설명은 클래식 new-york-v4 스타일에는 아직 맞지만, 신규 프리셋 시스템 전체를 대표하지 않는다.

함정 ③ "복사된 파일 하나만 보면 스타일을 전부 파악할 수 있다"

클래식 new-york-v4 스타일은 맞는 말이다 — bg-primary hover:bg-primary/90 같은 Tailwind 클래스가 컴포넌트 파일 안에 그대로 보인다. 하지만 신규 프리셋 베이스(radix/base/aria)의 컴포넌트는 cn-button-variant-default처럼 의미만 담은 이름을 쓰고, 실제 색상·간격 값은 style-nova.css 같은 별도 스타일시트의 @apply 규칙 안에 있다. "컴포넌트 파일 하나가 스타일의 전부"라는 원조 철학이 이 신규 시스템에서는 스타일시트 파일까지 함께 봐야 하는 형태로 바뀌었다.

함정 ④ "패키지 이름이 shadcn-ui다"

GitHub 조직·저장소 이름은 여전히 shadcn-ui/ui이지만, 실제로 npx로 실행하고 npm에 배포되는 CLI 패키지 이름은 "shadcn"이다(packages/shadcn/package.json의 name 필드). 예전 자료에서 npx shadcn-ui@latest 같은 명령을 본 적이 있다면 구버전 이름이며, 현재는 npx shadcn@latest가 맞는 표기다.

함정 ⑤ "asChild만 알면 커스텀 트리거를 다 만들 수 있다"

Radix 기반 컴포넌트는 asChild prop으로 렌더링 요소를 갈아 끼우지만, Base UI 기반 컴포넌트는 같은 목적에 render prop을 쓴다. 저장소의 스킬 문서(skills/shadcn/rules/base-vs-radix.md)가 이 차이를 별도 문서로 떼어 다룰 만큼, 어떤 베이스를 쓰는 프로젝트인지 먼저 확인하지 않으면 다른 베이스의 문법을 그대로 갖다 써서 타입 에러를 만나기 쉽다.

검증 체계
레지스트리 스키마 검증 + 브라우저 테스트가 실제로 돈다

CI 워크플로 validate-registries.yml은 registry.json과 각 항목이 registryItemSchema(zod)를 만족하는지 매번 검증하고, browser-tests.yml은 실제 브라우저에서 컴포넌트 렌더링을 확인한다. test.yml은 레지스트리를 빌드한 뒤 v4:dev 서버를 띄우고 Vitest(.test.ts 111개)를 돌린다 — "복붙 코드니까 테스트가 허술하겠지"라는 짐작과 달리, 스키마·빌드·브라우저 3단 검증이 자동화돼 있다.

10대안 비교

MUI · Chakra UI · Mantine · daisyUI — "받는다" vs "설치한다"의 근본 차이
도구배포 방식 · 커스터마이징 한계
MUI (Material UI)npm 패키지 설치. Material Design을 충실히 구현한 완제품 컴포넌트 + 테마 API. 깊은 시각적 변경은 sx/테마 오버라이드를 배워야 하고, 컴포넌트 내부 마크업 구조 자체는 손댈 수 없다.
Chakra UInpm 패키지 설치. 접근성이 좋고 테마 토큰 시스템이 유연하지만, 여전히 "라이브러리의 컴포넌트 API"를 배우고 그 틀 안에서 커스터마이징한다.
Mantinenpm 패키지 설치. 컴포넌트 가짓수가 매우 많고 폼·차트 등 부가 패키지도 풍부하지만, 역시 완제품을 설치해 쓰는 전통적 라이브러리 모델이다.
daisyUITailwind CSS 플러그인(CSS 클래스 프리셋)이지 React 컴포넌트가 아니다. btn btn-primary 같은 클래스명만 제공하고 마크업·상호작용 로직·접근성은 직접 짜야 한다.
shadcn/uiCLI로 소스 코드를 프로젝트에 복사. 접근성·상호작용 로직은 Radix/Base UI/React Aria가 이미 검증해 뒀고, 마크업과 스타일은 전부 내 코드로 들어와 자유롭게 편집 가능. 대신 업데이트는 자동이 아니라 shadcn diff로 수동 추적.

선택 기준을 한 문장으로 요약하면 이렇다. "빠르게 완제품을 쓰고 싶고 깊은 커스터마이징은 드물다"면 MUI·Chakra·Mantine 같은 전통 라이브러리가 유지보수 부담이 적다. "디자인을 세밀하게 통제하고 싶고, 컴포넌트 내부 구현까지 언제든 고칠 각오가 있다"면 shadcn/ui의 "코드를 내가 소유한다"는 모델이 압도적으로 유리하다. daisyUI는 애초에 React 컴포넌트 레이어가 없으므로 비교 대상이라기보다 "Tailwind 클래스 프리셋"이라는 다른 층위의 도구다.

11정리 · 누가 쓰면 좋은가

"디자인 시스템의 시작점"이 필요한 팀 · AI 코드 생성 워크플로를 쓰는 개발자

shadcn/ui를 한 문장으로 다시 정리하면: "검증된 헤드리스 접근성 엔진 위에, 내가 100% 소유하고 고칠 수 있는 스타일 코드를 CLI가 대신 재단해 붙여주는 레지스트리 시스템"이다. npm 라이브러리처럼 설치해서 쓰는 게 아니라, 목공소에서 치수 맞춰 받은 가구처럼 "완성된 내 코드"로 프로젝트에 들어온다는 점이 다른 모든 특징의 출발점이다.

이 저장소에서 배울 게 많은 사람은 다음과 같다. 첫째, 사내 디자인 시스템을 처음부터 만들어야 하는 팀 — Radix/Base UI/React Aria 위에서 접근성을 걱정하지 않고 스타일링부터 시작할 수 있는 뼈대로 쓰기 좋다. 둘째, AI 코드 생성 도구를 실무에 쓰는 개발자 — v0·Claude Code 같은 도구가 사실상 표준 출력으로 삼는 형식이라, 이 저장소의 CLI 변형 로직과 MCP 서버를 이해해 두면 에이전트가 왜 이런 파일 구조로 코드를 내놓는지 납득할 수 있다. 셋째, ts-morph를 이용한 AST 코드 변형이나 CLI 설계를 공부하려는 개발자 — transform-import.ts·preset.ts는 "설정에 따라 소스 코드를 안전하게 재작성하는" 실전 패턴의 좋은 교재다.

반대로 "설치 한 번으로 끝내고 다시는 그 코드를 보고 싶지 않다"는 팀에는 맞지 않는다. 컴포넌트가 내 코드가 됐다는 건 결국 그 코드의 유지보수 책임도 내가 진다는 뜻이며, 이 트레이드오프를 받아들일 준비가 됐을 때 shadcn/ui의 진짜 가치가 드러난다.

12직접 해볼 수 있는 실습 과제

버튼 수정부터 별칭 변형·프리셋 검증·레지스트리 빌드까지

아래 과제는 독자가 별도 학습용 복사본에서 수행한다. 명령은 저장소 루트 기준이며, 해당 워크스페이스의 의존성이 이미 설치되어 있어야 한다. 원격 레지스트리 조회 없이 로컬 코드와 테스트로 결과를 확인할 수 있다.

경로는 제공된 소스를 기준으로 적었다. 원문에서 CLI 내부로 설명한 변형기와 프리셋 구현은 이 소스에서 packages/registry/src/ 아래에 있으므로, packages/shadcn/과 함께 읽는다.

입문 과제

버튼에 나만의 변형 옵션 추가하기

목표: 컴포넌트 소스를 직접 소유한다는 의미를 체험하고, variant와 size가 클래스 문자열로 바뀌는 과정을 이해한다.

  1. apps/v4/registry/new-york-v4/ui/button.tsx의 buttonVariants와 defaultVariants를 읽는다. 사용 예시는 apps/v4/registry/new-york-v4/examples/button-demo.tsx에서 확인한다.
  2. buttonVariants의 variants.variant 안에 아래 항목을 추가한다. 기존 옵션과 기본값은 유지한다.
brand: "bg-primary text-primary-foreground underline underline-offset-4",
  1. 다음 명령으로 새 옵션과 작은 크기를 함께 선택했을 때의 클래스를 출력한다. 이후 variant를 default와 outline으로 바꿔 다시 비교한다.
pnpm --filter=v4 exec tsx -e 'import { buttonVariants } from "./registry/new-york-v4/ui/button.tsx"; console.log(buttonVariants({ variant: "brand", size: "sm" }))'

완료 기준: 새 옵션의 출력에 underline·underline-offset-4와 작은 크기의 h-8이 함께 나타난다. 기존 옵션과 비교해 어떤 클래스가 공통이고 어떤 클래스가 선택에 따라 달라지는지 설명할 수 있다.

중급 과제

import 별칭 변형에 회귀 테스트 추가하기

목표: 레지스트리 내부 경로를 사용자 프로젝트 경로로 옮기면서, 외부 패키지 이름은 유지하는지 검증한다.

  1. apps/v4/components.json의 aliases를 확인한 뒤, packages/registry/src/transformers/transform-import.ts의 updateImportAliases()를 읽는다.
  2. packages/registry/src/transformers/transform-import.test.ts에 다음 테스트를 추가한다. 예시는 이 파일에 이미 있는 transform·Config·Vitest 가져오기를 사용한다.
it("학습: 내부 별칭만 바꾸고 외부 패키지는 유지한다", async () => {
  const result = await transform({
    filename: "learning.tsx",
    raw: `import { Button } from "@/registry/new-york-v4/ui/button"
import { useState } from "react"
`,
    config: {
      tsx: true,
      aliases: {
        components: "~/components",
        ui: "~/ui",
        utils: "~/lib/utils",
      },
    } as Config,
  })

  expect(result).toContain('from "~/ui/button"')
  expect(result).toContain('from "react"')
  expect(result).not.toContain("@/registry/")
})
  1. 해당 테스트 파일을 실행한다. 입력의 경로, 설정의 별칭, 출력의 경로를 한 줄씩 대응시켜 기록한다.
pnpm --filter=@shadcn/registry test src/transformers/transform-import.test.ts

완료 기준: 기존 테스트와 새 테스트가 모두 통과한다. Button의 경로는 ~/ui/button으로 바뀌고, react는 그대로 남는 이유를 코드의 분기와 연결해 설명할 수 있다.

중급 과제

프리셋이 같은 설정으로 돌아오는지 검증하기

목표: 설정을 짧은 코드로 바꿨다가 복원하는 과정과, 과거 코드의 의미를 지켜야 하는 이유를 이해한다.

  1. packages/registry/src/preset/preset.ts의 값 배열과 호환성 규칙을 읽는다. 이어서 같은 폴더의 preset.test.ts에서 기본 설정의 왕복 테스트를 찾는다.
  2. preset.test.ts에 다음 사례를 추가한다. 필요한 함수·상수·타입은 이 파일에서 이미 가져오고 있다.
it("학습용 프리셋을 같은 설정으로 복원한다", () => {
  const config: PresetConfig = {
    ...DEFAULT_PRESET_CONFIG,
    style: "vega",
    baseColor: "zinc",
    theme: "amber",
  }

  const code = encodePreset(config)
  expect(decodePreset(code)).toEqual(config)
})
  1. 테스트를 실행한 뒤, 값 배열을 재정렬하는 경우와 뒤에 값을 추가하는 경우가 기존 코드 해석에 어떤 차이를 만드는지 적는다.
pnpm --filter=@shadcn/registry test src/preset/preset.test.ts

완료 기준: 새 사례와 기존 프리셋 테스트가 모두 통과한다. 복원된 객체가 선택한 항목뿐 아니라 나머지 기본 설정도 유지하며, 배열의 순서가 저장 형식의 일부라는 점을 설명할 수 있다.

심화 과제

학습용 컴포넌트를 레지스트리 JSON으로 만들기

목표: 파일 경로를 담은 레지스트리 정의가 실제 소스 내용까지 포함한 배포용 JSON으로 바뀌는 과정을 재현한다.

  1. packages/shadcn/src/commands/build.ts와 packages/registry/src/registry/loader.ts를 읽고, 파일을 읽는 부분과 JSON을 쓰는 부분을 구분한다.
  2. packages/shadcn/src/commands/build.test.ts에 아래 테스트를 추가한다. 기존 createFixture()가 임시 폴더에 학습용 파일을 만들며, 아래 경로는 이 테스트에서 새로 만드는 경로다.
it("학습 카드를 로컬 레지스트리 JSON으로 만든다", async () => {
  const source =
    'export function LearningCard() { return <div>학습 카드</div> }'

  const cwd = await createFixture({
    "registry.json": JSON.stringify({
      name: "learning",
      homepage: "http://localhost",
      items: [
        {
          name: "learning-card",
          type: "registry:ui",
          files: [
            {
              path: "components/ui/learning-card.tsx",
              type: "registry:ui",
            },
          ],
        },
      ],
    }),
    "components/ui/learning-card.tsx": source,
  })

  await build.parseAsync(
    ["node", "shadcn", "registry.json", "--cwd", cwd, "--output", "public/r"],
    { from: "node" }
  )

  const item = JSON.parse(
    await fs.readFile(
      path.join(cwd, "public/r/learning-card.json"),
      "utf-8"
    )
  )

  expect(item.files[0].path).toBe("components/ui/learning-card.tsx")
  expect(item.files[0].content).toBe(source)
})
  1. 해당 테스트 파일을 실행한다. homepage는 메타데이터이며, 이 사례는 로컬 파일을 읽고 JSON을 만드는 동작을 검증한다.
pnpm --filter=shadcn test src/commands/build.test.ts

완료 기준: 기존 빌드 테스트와 새 테스트가 모두 통과한다. 생성된 항목에 원본 경로와 소스 내용이 함께 들어가며, 이 빌드가 사용자 앱에 컴포넌트를 추가하는 add 명령과 어떻게 다른지 설명할 수 있다.

13관련 기술 심화 학습 로드맵

컴포넌트를 읽는 눈에서 코드를 배포하고 변형하는 설계까지, 6주 과정

매주 하나의 결과물을 남기며 진행한다. 화면을 만드는 기술을 먼저 익히고, 그 화면의 소스를 프로젝트에 맞게 옮기는 CLI와 레지스트리로 범위를 넓힌다.

주차배울 개념이 레포에서 읽을 곳결과물
1주차 React 속성 전달, TypeScript의 속성 타입 조합, cva의 옵션과 기본값 apps/v4/registry/new-york-v4/ui/button.tsx
apps/v4/registry/new-york-v4/examples/button-size.tsx
버튼 옵션별 클래스 대응표와 실습 1의 새 변형 옵션
2주차 Tailwind 유틸리티, 시맨틱 토큰, CSS 변수, 컴포넌트와 스타일 정의의 분리 apps/v4/registry/bases/base/ui/button.tsx
apps/v4/registry/styles/style-nova.css
apps/v4/app/globals.css
cn-button-variant-default에서 bg-primary를 거쳐 --primary까지 이어지는 추적도
3주차 헤드리스 컴포넌트, 요소 합성, 다이얼로그의 제목·설명과 접근성 apps/v4/registry/bases/radix/ui/dialog.tsx
apps/v4/registry/bases/base/ui/dialog.tsx
apps/v4/examples/radix/dialog-demo.tsx
apps/v4/examples/base/dialog-demo.tsx
같은 트리거를 asChild와 render로 표현한 코드 쌍과, 제목·설명·닫기 버튼의 역할표
4주차 CLI 명령 처리, 프로젝트 설정, import 별칭과 구문 구조에 따른 코드 변형 apps/v4/components.json
packages/shadcn/src/commands/add.ts
packages/registry/src/transformers/transform-import.ts
packages/registry/src/transformers/transform-import.test.ts
실습 2의 회귀 테스트와 입력·설정·출력 비교표
5주차 스키마 검증, 레지스트리 항목의 파일 정보, npm 의존성과 레지스트리 의존성의 차이 packages/registry/src/registry/schema.ts
packages/registry/src/registry/loader.ts
packages/shadcn/src/commands/build.ts
packages/shadcn/src/commands/build.test.ts
실습 4의 빌드 테스트와 소스 파일·항목 정의·생성 JSON의 대응표
6주차 프리셋 직렬화와 호환성, 왕복 검증, MCP 도구의 입력과 응답 packages/registry/src/preset/preset.ts
packages/registry/src/preset/preset.test.ts
packages/shadcn/src/mcp/index.ts
실습 3의 왕복 테스트와 search_items_in_registries의 입력·처리·응답 흐름도
학습 순서의 이유

처음에는 버튼 하나가 어떻게 만들어지는지 보고, 다음에는 같은 버튼을 다른 스타일과 동작 엔진으로 표현하는 법을 배운다. 그다음 그 소스를 옮기는 변형기, 묶어서 나누는 레지스트리, 설정을 공유하는 프리셋과 도구 인터페이스를 읽으면 각 계층의 책임이 연결된다.

14핵심 키워드 사전

소스를 읽다가 다시 찾아볼 수 있는 핵심 개념 13개
모노레포
여러 앱과 패키지를 하나의 저장소에서 함께 관리하는 구조다. 이 레포는 pnpm-workspace.yaml로 apps/*와 packages/*를 묶고, 워크스페이스를 골라 명령을 실행한다.
레지스트리와 레지스트리 항목
레지스트리는 나눠줄 코드의 목록이고, 항목은 이름·타입·파일·의존성 등을 담은 개별 단위다. 이 레포에서는 apps/v4/registry/의 소스와 항목 정의를 바탕으로 CLI가 소비할 JSON을 만든다.
components.json
CLI에 프로젝트의 스타일·CSS 위치·코드 경로 별칭 등을 알려주는 설정 파일이다. apps/v4/components.json을 보면 문서 앱 자체도 어떤 설정으로 컴포넌트 경로를 관리하는지 확인할 수 있다.
import 별칭
긴 파일 경로에 붙이는 짧은 이름으로, @/components나 ~/ui처럼 표현한다. 이 레포의 transform-import.ts는 레지스트리 경로를 설정된 별칭에 맞춰 다시 작성한다.
헤드리스 컴포넌트
상태·상호작용·접근성 같은 동작을 제공하고, 시각적 표현은 사용하는 쪽에서 정하도록 만든 컴포넌트다. 이 레포의 apps/v4/registry/bases/에서는 서로 다른 동작 엔진 위에 스타일과 컴포넌트 구성을 더하는 방식을 비교할 수 있다.
슬롯과 요소 합성
동작을 제공하는 컴포넌트와 실제 화면에 그릴 요소를 결합하는 방법이다. 이 레포의 Radix 다이얼로그 예시는 asChild로 자식 버튼을 사용하고, Base UI 예시는 render에 버튼 요소를 전달한다.
cva
class-variance-authority는 이름 붙은 옵션 조합에 따라 클래스 문자열을 고르는 도구다. 버튼의 buttonVariants는 variant와 size를 받아 공통 클래스와 선택한 옵션의 클래스를 조합한다.
시맨틱 토큰
구체적인 색상값 대신 역할을 나타내는 이름으로 디자인 값을 참조하는 방식이다. 이 레포의 bg-primary는 apps/v4/app/globals.css의 테마 연결을 통해 --primary 값을 사용하므로, 컴포넌트마다 색상값을 직접 적을 필요가 줄어든다.
AST와 코드 변형기
AST는 소스를 import문·함수·표현식 같은 구문 구조로 나타낸 데이터이며, 변형기는 그 구조를 이용해 코드를 고친다. 이 레포의 packages/registry/src/transformers/는 경로·클래스·렌더링 관련 코드를 프로젝트 설정에 맞게 바꾸는 역할을 나눠 맡는다.
dependencies와 registryDependencies
dependencies는 필요한 npm 패키지를, registryDependencies는 함께 가져와야 할 레지스트리 항목을 나타낸다. 이 레포의 registry/schema.ts는 두 필드를 구분해 정의하므로, 패키지 설치와 소스 항목 추가를 별개의 의존 관계로 읽어야 한다.
프리셋과 왕복 검증
프리셋은 스타일·색상 등 여러 선택을 하나의 공유 가능한 설정으로 묶은 것이고, 왕복 검증은 이를 코드로 바꿨다가 복원했을 때 같은 설정인지 확인하는 테스트다. 이 레포의 encodePreset()과 decodePreset()는 짝으로 동작하며, 값 배열의 순서를 지켜 기존 코드의 의미를 유지한다.
테스트 픽스처
테스트가 일정한 조건에서 동작하도록 준비하는 작은 프로젝트나 입력 파일 묶음이다. 이 레포는 packages/tests/fixtures/에 예제 프로젝트를 두며, build.test.ts의 createFixture()처럼 필요한 파일을 임시 폴더에 만드는 방식도 사용한다.
MCP
AI 에이전트가 정해진 입력 형식으로 도구를 호출하고 결과를 받도록 연결하는 프로토콜이다. 이 레포의 packages/shadcn/src/mcp/index.ts는 search_items_in_registries와 같은 도구를 정의해, 에이전트가 레지스트리를 탐색할 수 있게 한다.