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을 실행하면:

  1. components.json 재생성 (기존 값이 있으면 --force 없이는 스킵)
  2. index.css(또는 globals.css) 무조건 재생성
  3. cn 유틸리티 함수 재생성
  4. 필요한 의존성 재설치

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가 바뀔 수 있다 ✨