shadcn init 실행했더니 프라이머리 컬러가 검정으로 — CSS 변수 덮어쓰기 트러블슈팅
shadcn init 실행 후 커스텀 CSS 변수가 기본값으로 덮어씌워지는 문제의 원인과 해결법을 정리합니다. 디자인 토큰이 초기화되는 근본 원인을 분석하고, git diff 기반 복구 및 예방 패턴을 코드와 함께 설명합니다.
💡 Tip. 바쁜 현대인들을 위한 본문 요약
shadcn init실행 시 index.css의 CSS 변수가 기본값으로 전체 덮어씌워짐- 커스텀
--primary,--nav-background등 디자인 토큰이 무경고로 초기화됨- 즉각 해결:
git checkout src/index.css후 필요한 변수만 수동 추가- 장기 예방: init 전
git stash+ init 후git diff필수 습관화- shadcn CLI의
--force플래그 없이도 CSS 파일은 덮어쓰는 구조 — 이걸 모르면 반복해서 당한다
Chart 컴포넌트가 필요해서 shadcn init을 돌렸다.
끝나고 브라우저를 확인하니 프라이머리 버튼이 전부 검정색이었다.
“뭐지? 빌드 에러인가?” 싶어서 콘솔을 열었는데 에러는 없었다. CSS 변수가 바뀌어 있었다.
🔍 증상: 버튼이 갑자기 검정색이 됐다
⚠️ 주의: shadcn init은
index.css를 통째로 덮어쓴다. 컴포넌트 추가(shadcn add)와 달리, init은 프로젝트 초기 설정을 다시 수행하는 명령이다.
❌ 발생한 증상
프라이머리 컬러로 오렌지(#ff9100)를 쓰고 있었다.
init 실행 후 모든 bg-primary 요소가 거의 검정에 가까운 색으로 바뀌었다.
/* 덮어쓰기 전 (우리 디자인 토큰) */
:root {
--primary: 33 100% 50%; /* #ff9100 오렌지 */
--primary-foreground: 0 0% 100%;
--nav-background: 33 100% 45%;
--nav-foreground: 0 0% 100%;
}
/* shadcn init이 덮어쓴 값 */
:root {
--primary: 0 0% 9%; /* 거의 검정 */
--primary-foreground: 0 0% 98%;
/* --nav-background → 사라짐 */
/* --nav-foreground → 사라짐 */
}
커스텀으로 추가했던 --nav-background, --nav-foreground 변수는 아예 삭제됐다.
shadcn 기본 테마에 없는 변수는 전부 날아간 거다.
📌 핵심: shadcn init은 CSS 파일의 일부를 수정하는 게 아니라 전체를 새로 생성한다. 기존 커스텀 변수가 있든 없든, 기본 테마 템플릿으로 교체해버린다.
🕵️ 탐색: 왜 경고도 없이 덮어쓰는 걸까
가설 1: --force 옵션을 줬나?
처음엔 내가 --force 플래그를 실수로 넣었나 의심했다.
터미널 히스토리를 확인해보니 그냥 pnpm dlx shadcn@latest init이었다.
shadcn init의 CLI 옵션을 보면 -f, --force가 있다.
하지만 이건 components.json 같은 설정 파일을 덮어쓸 때 쓰는 옵션이다.
# CLI 도움말 발췌
Options:
-f, --force force overwrite of existing configuration. (default: false)
--css-variables use css variables for theming. (default: true)
--force를 안 줬어도 CSS 파일은 덮어쓴다.
이 옵션은 components.json 보호용이지, index.css 보호용이 아니다.
🔍 포인트:
--force플래그의 보호 범위는components.json이다. CSS 파일은 init 과정에서 항상 재생성 대상이다.
가설 2: CSS 변수 부분만 패치하는 건 아닐까?
혹시 기존 :root 블록에 새 변수만 추가하는 방식은 아닐까 생각했다.
git diff를 확인해보니 완전히 다른 파일이었다.
$ git diff src/index.css | head -40
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
-@layer base {
- :root {
- --background: 0 0% 100%;
- --foreground: 222.2 84% 4.9%;
- --primary: 33 100% 50%;
- --primary-foreground: 0 0% 100%;
- --nav-background: 33 100% 45%;
+@import "tailwindcss";
+
+@custom-variant dark (&:is(.dark *));
+
+@theme inline {
+ --color-background: hsl(var(--background));
+ --color-foreground: hsl(var(--foreground));
파일 구조 자체가 바뀌어 있었다. shadcn의 최신 버전이 Tailwind CSS v4 형식으로 CSS를 재생성한 거다.
근본 원인: init = 프로젝트 재초기화
shadcn CLI의 init 명령은 말 그대로 초기화다.
이미 설정된 프로젝트에서 다시 init을 실행하면:
components.json재생성 (기존 값이 있으면--force없이는 스킵)index.css(또는globals.css) 무조건 재생성cn유틸리티 함수 재생성- 필요한 의존성 재설치
2번이 함정이다.
CSS 파일은 --force 플래그와 무관하게 항상 덮어쓴다.
📊 데이터: shadcn/ui GitHub Issues에서 “init css overwrite”로 검색하면 12개 이상의 관련 이슈가 나온다.
#4841,#10028등 반복적으로 보고되는 문제다.
이건 shadcn CLI의 의도된 동작이다.
init은 “처음 설정”을 위한 명령이지, “업데이트”를 위한 명령이 아니다.
문제는 Chart나 Sidebar 같은 일부 컴포넌트가 init을 요구한다는 점이다.
🛠️ 해결: git checkout + 수동 머지
✅ Step 1: CSS 파일 원복
가장 빠른 방법은 git으로 되돌리는 거다.
# index.css를 마지막 커밋 상태로 복구
git checkout src/index.css
이러면 커스텀 디자인 토큰이 전부 살아난다. 단, shadcn init이 추가하려던 새 변수(chart 컬러 등)는 없는 상태다.
✅ Step 2: 필요한 변수만 수동 추가
init이 추가하려던 변수를 확인하고, 필요한 것만 골라 넣는다.
# init이 생성한 CSS를 임시 파일에 저장
git stash
pnpm dlx shadcn@latest init -y
cp src/index.css /tmp/shadcn-default.css
git stash pop
/* /tmp/shadcn-default.css에서 필요한 변수만 발췌 */
:root {
/* ... 기존 커스텀 변수 유지 ... */
/* chart 변수 추가 (shadcn chart 컴포넌트용) */
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
💡 팁:
diff /tmp/shadcn-default.css src/index.css로 차이를 비교하면 어떤 변수가 새로 추가됐는지 한눈에 파악할 수 있다.
✅ Step 3: 다크 모드 변수도 확인
:root만 복구하고 .dark 블록을 빼먹으면 다크 모드에서 깨진다.
.dark 블록도 동일한 패턴으로 필요한 변수만 추가한다.
.dark {
/* ... 기존 다크모드 커스텀 변수 유지 ... */
/* chart 다크모드 변수 추가 */
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
⚠️ 주의: Tailwind CSS v4에서는 CSS 변수 형식이 HSL(
33 100% 50%)에서 OKLCH(oklch(0.205 0 0))로 바뀌었다. shadcn 최신 버전을 쓰고 있다면, 기존 HSL 변수와 새 OKLCH 변수가 충돌하지 않는지 확인해야 한다.
✅ 검증: 복구 후 확인 포인트
복구했으면 눈으로만 확인하지 말고 체계적으로 검증한다.
시각적 검증
# 개발 서버 재시작
pnpm dev
확인할 항목:
- 프라이머리 버튼 색상 (오렌지
#ff9100이 맞는지) - 네비게이션 바 배경색 (
--nav-background) - 다크 모드 토글 시 색상 전환
- 새로 추가한 chart 컴포넌트 렌더링
DevTools로 변수 확인
// Chrome DevTools Console에서
getComputedStyle(document.documentElement).getPropertyValue('--primary')
// 결과: " 33 100% 50%" ← 오렌지 맞으면 OK
📌 핵심: 브라우저 DevTools의 Computed 탭에서
--primary값을 직접 확인하는 게 가장 정확하다. 눈으로 보면 비슷한 색이 다른 값일 수 있다.
git diff 최종 확인
$ git diff src/index.css
변경사항이 chart 변수 추가분만 있어야 한다. 커스텀 변수가 삭제되거나 값이 바뀌어 있으면 안 된다.
🛡️ 예방: 다시는 당하지 않는 체크리스트
규칙 1: init 대신 add를 쓴다
대부분의 경우 shadcn init을 다시 실행할 필요가 없다.
컴포넌트 추가는 shadcn add로 충분하다.
# ❌ 이렇게 하지 않는다
pnpm dlx shadcn@latest init
# ✅ 컴포넌트만 추가한다
pnpm dlx shadcn@latest add chart
pnpm dlx shadcn@latest add sidebar
add 명령은 CSS 파일을 건드리지 않는다.
컴포넌트 파일과 의존성만 추가한다.
💡 팁: shadcn CLI 공식 문서에서도 init은 “프로젝트 초기 설정”용이라고 명시하고 있다. 이미 init을 한 프로젝트에서는 add만 쓰면 된다. — shadcn/ui CLI 문서
규칙 2: init이 불가피하면 반드시 백업
Chart나 Sidebar 등 일부 컴포넌트는 components.json에 특정 설정이 필요해서 init을 요구하는 경우가 있다.
이때는 아래 순서를 따른다.
# 1. 작업 중인 변경사항 저장
git stash
# 2. CSS 파일 백업
cp src/index.css src/index.css.bak
# 3. init 실행
pnpm dlx shadcn@latest init -y
# 4. CSS 파일 복구
cp src/index.css.bak src/index.css
# 5. 새로 필요한 변수만 수동 추가
# diff src/index.css.bak /tmp/shadcn-default.css 참고
# 6. 백업 파일 정리
rm src/index.css.bak
git stash pop
규칙 3: CI/CD에 디자인 토큰 검증 추가
프로덕션 빌드 전에 핵심 CSS 변수가 존재하는지 체크하는 스크립트를 추가한다.
#!/bin/bash
# scripts/check-design-tokens.sh
REQUIRED_VARS=(
"--primary"
"--primary-foreground"
"--nav-background"
"--nav-foreground"
)
CSS_FILE="src/index.css"
for var in "${REQUIRED_VARS[@]}"; do
if ! grep -q "$var" "$CSS_FILE"; then
echo "❌ Missing CSS variable: $var"
exit 1
fi
done
echo "✅ All design tokens present"
// package.json
{
"scripts": {
"check:tokens": "bash scripts/check-design-tokens.sh",
"prebuild": "pnpm check:tokens"
}
}
📊 데이터: Stack Overflow Developer Survey 2025에 따르면 프론트엔드 개발자의 62%가 CSS 프레임워크를 사용하며, 그 중 Tailwind CSS 사용률은 31%로 2위다. shadcn/ui가 Tailwind 기반인 만큼, 이런 종류의 설정 충돌은 적지 않은 개발자가 겪을 수 있는 문제다.
규칙 4: 커스텀 변수는 별도 파일로 분리
근본적인 예방법은 커스텀 디자인 토큰을 index.css와 분리하는 거다.
/* src/styles/design-tokens.css */
:root {
/* 프로젝트 커스텀 디자인 토큰 */
--primary: 33 100% 50%;
--primary-foreground: 0 0% 100%;
--nav-background: 33 100% 45%;
--nav-foreground: 0 0% 100%;
}
.dark {
--primary: 33 100% 60%;
--primary-foreground: 0 0% 100%;
--nav-background: 33 100% 55%;
--nav-foreground: 0 0% 100%;
}
/* src/index.css — 올바른 순서 */
/* shadcn 기본 → 커스텀 오버라이드 */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
/* shadcn 기본 변수 — init이 덮어써도 커스텀 토큰은 안전 */
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
/* ... */
}
}
/* 커스텀 토큰은 @layer 밖에서 import (높은 우선순위) */
@import "./styles/design-tokens.css";
CSS 캐스케이드 특성상 @layer 밖의 스타일이 @layer base 안의 스타일보다 우선한다.
shadcn init이 @layer base 안의 변수를 덮어써도, 커스텀 토큰은 별도 파일에서 더 높은 우선순위로 적용된다.
⚠️ 주의:
@import의 위치와@layer의 우선순위 관계를 정확히 이해해야 한다. Tailwind CSS 공식 문서에서 커스텀 테마 변수 관리 방법을 참고하자.
📋 정리: shadcn init CSS 변수 덮어쓰기 대응표
| 상황 | 안티패턴 | 권장 패턴 |
|---|---|---|
| 새 컴포넌트 추가 | shadcn init 재실행 | shadcn add <component> |
| init이 불가피할 때 | 그냥 실행 후 “어 색 바뀌었네” | git stash → init → git checkout index.css → 필요 변수만 수동 추가 |
| 커스텀 디자인 토큰 관리 | index.css에 직접 작성 | 별도 design-tokens.css로 분리 |
| 변수 삭제 감지 | 배포 후 눈으로 확인 | prebuild 스크립트에 토큰 검증 추가 |
| 팀 프로젝트 협업 | 구두 공유 “init 조심해” | CONTRIBUTING.md에 init 금지 규칙 명문화 |
shadcn init은 한 번만 쓰는 명령이다.
두 번 쓸 일이 생기면, 그건 add로 해결할 수 있는 걸 init으로 하고 있을 확률이 높다.
디자인 토큰은 코드베이스의 시각적 계약이다.
CI에서 검증하지 않으면, 누군가의 init 한 방에 전체 UI가 바뀔 수 있다 ✨
📚 React 프론트엔드 삽질기 시리즈 (9편)
- 1. Vite 6.x 프록시에서 PATCH만 CORS 에러? 소문자 메서드 함정과 해결법
- 2. React Admin DataProvider 커스터마이징 삽질기
- 3. BE 응답 래퍼 언래핑 패턴 — API 200인데 왜 에러?
- 4. React useEffect 비동기 cleanup이 GPU를 죽이는 과정 — Pixi.js RenderTexture 실종 사건
- 5. shadcn init 실행했더니 프라이머리 컬러가 검정으로 — CSS 변수 덮어쓰기 트러블슈팅
- 6. Framer Motion whileInView 애니메이션이 스크린샷에서 사라지는 이유와 해결법
- 7. react-hook-form + Zod 연동에서 겪는 실전 함정 6가지 — 에러가 안 뜨는 이유부터 타입 불일치까지
- 8. Refine useCustom config.query가 정수를 보장하지 않는 함정 — 타입은 number인데 왜 400이야?
- 9. 패키지 설치 후 Invalid hook call? Vite 캐시 무효화가 답이다