shadcn/ui 오류 해결: 스타일 충돌, 렌더링 실패, 타입 오류

화면의 버튼 컴포넌트를 바라봅니다. 멋진 파란색 버튼이어야 하는데, 지금은 둥근 모서리조차 없는 평범한 HTML button처럼 보입니다.
지난 3개월 동안 작성한 코드보다 더 많은 문제를 겪었습니다. 스타일 충돌, 컴포넌트 렌더링 실패, TypeScript 오류는 마치 shadcn/ui의 단골 문제처럼 새 프로젝트마다 몇 가지씩 나타납니다.
이런 흔한 문제와 해결 방법을 정리했습니다. 같은 시행착오를 조금이라도 줄이는 데 도움이 되기를 바랍니다.
스타일 충돌 문제 해결
스타일 충돌은 전체 문제의 약 40%를 차지할 정도로 가장 흔합니다. 주요 원인은 몇 가지로 나뉩니다.
CSS 변수 충돌
shadcn/ui는 globals.css에 정의된 CSS 변수로 테마 색상을 관리합니다.
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 2%;
}
프로젝트에 자체 테마 설정이 이미 있거나 shadcn/ui를 설치하기 전에 Tailwind 색상 설정을 수정했다면 두 설정이 충돌할 수 있습니다.
어떻게 확인할까요?
먼저 globals.css를 열어 CSS 변수가 모두 있는지 살펴봅니다. 그런 다음 tailwind.config.js의 colors 설정을 확인합니다.
module.exports = {
theme: {
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
},
},
},
}
양쪽 설정이 서로 대응해야 합니다. 변수가 하나라도 빠지면 해당 스타일이 적용되지 않습니다.
경험상 가장 안전한 방법은 shadcn/ui를 설치하기 전에 tailwind.config.js와 globals.css를 백업하는 것입니다. 설치가 끝나면 두 파일의 차이를 비교하고 덮어써진 설정을 직접 복원하세요.
Shadow DOM과 Tailwind의 충돌
Shadow DOM은 원래 스타일을 격리하는 용도지만, Tailwind 클래스 이름은 Shadow DOM 경계를 통과할 수 없습니다.
대표적인 사례가 Dialog 컴포넌트입니다. DialogContent는 Portal을 통해 document.body에 렌더링되므로 Shadow DOM 범위 밖으로 나갑니다. 그 결과 스타일이 모두 사라집니다.
해결 방법은 두 가지입니다.
첫 번째는 Shadow DOM을 사용하지 않는 것입니다.
const MyDialogWC = r2wc(MyDialog, {
shadow: null // Shadow DOM 비활성화
});
이렇게 하면 Portal은 정상적으로 작동하지만 스타일 격리는 사라집니다. 전역 스타일을 직접 관리하고 클래스 이름 충돌도 주의해야 합니다.
두 번째는 Safelist로 필요한 클래스 이름을 강제로 포함하는 것입니다.
// tailwind.config.js
module.exports = {
safelist: [
'bg-primary',
'text-primary-foreground',
'hover:bg-primary/90',
'bg-red-500',
'h-9',
'h-10',
'px-3',
'px-4',
],
}
이 방법은 스타일 생성을 보장하지만 Safelist 때문에 CSS 파일 크기가 커집니다. 프로젝트 상황에 맞춰 선택해야 합니다.
다른 UI 라이브러리와 함께 사용하기
프로젝트에서 이미 MUI(Material-UI)를 사용하고 있고 shadcn/ui로 마이그레이션하려 한다면 스타일 충돌이 생길 수 있습니다.
문제의 원인은 모든 브라우저 기본 스타일을 초기화하는 Tailwind의 Preflight입니다. 이 과정에서 MUI 스타일도 초기화되어 컴포넌트가 비정상적으로 표시될 수 있습니다.
흔히 시도하는 방법은 Preflight를 비활성화하는 것입니다.
module.exports = {
corePlugins: {
preflight: false, // Preflight 비활성화
},
}
하지만 Tailwind 스타일도 영향을 받는 부작용이 있습니다. 일부 컴포넌트가 정상적으로 표시되지 않을 수 있습니다.
더 나은 방법은 Tailwind의 prefix 기능으로 모든 Tailwind 클래스에 접두사를 붙이는 것입니다.
module.exports = {
prefix: 'tw-', // 모든 클래스가 tw-bg-blue-500 형식으로 변경됨
}
이렇게 하면 Tailwind 클래스와 MUI 클래스가 충돌하지 않습니다. 다만 모든 클래스 이름 앞에 tw-를 직접 붙여야 해서 번거로울 수 있습니다.
권장 방식: 프로젝트에 MUI 컴포넌트가 이미 많다면 서둘러 전부 마이그레이션하지 마세요. 먼저 prefix 방식으로 두 라이브러리를 함께 사용하고, 새 컴포넌트에는 shadcn/ui를 적용하면서 기존 컴포넌트를 천천히 바꾸는 편이 좋습니다.
Tailwind 설정 덮어쓰기
직접 여러 번 겪은 문제입니다.
npx shadcn-ui@latest init을 실행하면 Tailwind 설정 파일이 덮어써질 수 있습니다. 특히 plugins 배열이 문제입니다. 이전에 설정한 @tailwindcss/forms나 다른 플러그인이 사라질 수 있습니다.
증상은 분명합니다. 폼 입력 필드의 스타일이 갑자기 이상해지거나 일부 컴포넌트의 스타일이 전혀 표시되지 않습니다.
진단 순서:
- 설치 전에 백업한
tailwind.config.js를 엽니다. - 설치 후 설정 파일과 비교합니다.
- 사라진 플러그인을 다시 추가합니다.
module.exports = {
// ... 기타 설정
plugins: [
require("@tailwindcss/forms"), // 다시 추가
require("tailwindcss-animate"),
],
}
예방 방법: shadcn/ui를 설치하기 전에 설정 파일을 백업하세요. 또는 모든 플러그인을 기록하는 전용 설정 관리 스크립트를 사용하세요.
컴포넌트가 렌더링되지 않는 문제 해결
스타일에는 문제가 없는데 컴포넌트가 전혀 표시되지 않는 경우도 흔합니다.
Content 경로 설정 오류
Tailwind는 어떤 파일에서 클래스 이름을 사용하는지 알아야 해당 CSS를 생성할 수 있습니다. 이 경로는 tailwind.config.js의 content 필드에서 설정합니다.
대개 shadcn/ui 컴포넌트 디렉터리가 포함되지 않아서 문제가 발생합니다.
설정 확인:
module.exports = {
content: [
'./src/app/**/*.{ts,tsx}',
'./src/components/**/*.{ts,tsx}', // 반드시 포함
'./app/**/*.{ts,tsx}',
'./pages/**/*.{ts,tsx}',
],
}
컴포넌트를 node_modules 안의 UI 라이브러리에 두었다면 다음 경로도 추가해야 합니다.
content: [
// ... 기타 경로
'./node_modules/@your-ui-lib/**/*.{ts,tsx}',
]
경험상 유용한 습관은 새 컴포넌트 디렉터리를 만들 때마다 해당 경로를 content 설정에 추가하는 것입니다. 그렇지 않으면 Tailwind가 파일을 스캔하지 못해 클래스 이름에 대응하는 CSS를 생성하지 않습니다.
globals.css 경로 문제
shadcn/ui는 테마 변수를 정의할 CSS 파일이 필요합니다. 이 파일의 경로는 components.json에서 설정합니다.
경로가 잘못되었거나 globals.css 파일이 여러 개일 때 문제가 생깁니다.
진단 방법:
먼저 components.json을 확인합니다.
{
"style": "default",
"css": "src/app/globals.css", // 이 경로
}
그런 다음 다음 항목을 확인합니다.
- 이 파일이 실제로 존재하는가?
- 프로젝트에 globals.css가 하나만 있는가?
- globals.css를 메인 파일에서 올바르게 import했는가?
globals.css가 여러 개라면 불필요한 파일을 삭제하고 하나만 남깁니다.
import 확인:
Next.js 프로젝트에서는 globals.css를 app/layout.tsx 또는 pages/_app.tsx에서 import해야 합니다.
import '@/app/globals.css' // 또는 './globals.css'
import하지 않으면 CSS 변수가 적용되지 않아 컴포넌트 스타일이 모두 사라집니다.
CSS 변수 미정의
globals.css 파일은 있지만 변수가 정의되지 않은 경우도 있습니다.
가장 대표적인 사례가 다크 모드입니다. dark mode로 전환했을 때 컴포넌트 색상이 잘못 표시된다면 다크 모드용 CSS 변수가 설정되지 않았을 수 있습니다.
globals.css 확인:
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 2%;
}
.dark 클래스 아래에 변수를 정의해야 합니다. 그렇지 않으면 다크 모드 컴포넌트에 스타일이 적용되지 않습니다.
Tailwind v4의 특수한 경우:
Tailwind v4를 사용한다면 설정 방식이 다릅니다.
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-primary: var(--primary);
}
이 @theme inline 매핑이 반드시 있어야 합니다. 그렇지 않으면 Tailwind v4가 변수를 인식하지 못합니다.
import 경로의 대소문자 문제
직접 두 번이나 겪은 문제입니다.
Windows는 파일명의 대소문자를 구분하지 않지만 Linux/Mac은 구분합니다. 로컬 개발 환경에서는 정상이어도 프로덕션에 배포하면 오류가 발생할 수 있습니다.
일반적인 증상은 로컬에서는 컴포넌트가 렌더링되지만 서버에 배포한 뒤 모듈을 찾지 못하는 것입니다.
대표적인 오류:
// ❌ 잘못된 예: Button의 B가 대문자
import { Button } from "@/components/ui/Button"
// ✅ 올바른 예: button의 b가 소문자
import { Button } from "@/components/ui/button"
shadcn/ui 컴포넌트의 파일명은 모두 소문자입니다. import할 때도 소문자 경로를 사용해야 합니다.
진단 방법:
모든 컴포넌트 import 문을 확인해 경로와 실제 파일명이 일치하는지 점검하세요. 특히 프로덕션 환경의 오류 메시지를 자세히 살펴보세요.
TypeScript 타입 오류 해결
TypeScript 오류는 상대적으로 적지만 한번 발생하면 해결하기 까다롭습니다.
Variant 속성 타입 오류
shadcn/ui의 Button 컴포넌트에는 버튼 스타일(default, destructive, outline 등)을 전환하는 variant 속성이 있습니다.
일반적인 오류 메시지는 다음과 같습니다.
Type '{ variant: string }' is not assignable to type 'IntrinsicAttributes & ButtonProps'.
Property 'variant' does not exist on type 'IntrinsicAttributes & ButtonProps'.
문제 원인:
Button 컴포넌트의 타입 정의에서 variant 속성이 올바르게 노출되지 않았습니다.
진단 방법:
components/ui/button.tsx를 열어 variant 타입 정의를 확인합니다.
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
},
},
}
)
interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
// 이 VariantProps가 반드시 있어야 함
}
VariantProps<typeof buttonVariants>가 빠지면 variant 속성의 타입도 사라집니다.
경험상 먼저 확인할 부분은 컴포넌트의 타입 정의입니다. VariantProps를 올바르게 상속하는지 확인하세요.
React 버전 비호환
React 19를 사용하는데 일부 의존성이 아직 React 19를 지원하지 않는다면 타입 오류가 발생할 수 있습니다.
일반적인 오류 메시지는 다음과 같습니다.
npm error ERESOLVE unable to resolve dependency tree
npm error Found: [email protected]
해결 방법은 두 가지입니다.
첫 번째는 강제로 설치하는 것입니다.
npm install --legacy-peer-deps
# 또는
npm install --force
이렇게 하면 peer dependency의 버전 요구 사항을 무시하지만 호환성 문제가 생길 수 있습니다.
두 번째는 React 버전을 낮추는 것입니다.
npm install react@18 react-dom@18
React 18을 사용하고 의존성이 업데이트된 뒤 업그레이드합니다.
권장 방식: 새 프로젝트에서는 React 18을 사용하는 편이 안정적입니다. shadcn/ui와 다른 의존성이 React 19를 지원한 뒤 업그레이드를 고려하세요.
React Hook Form 타입 문제
shadcn/ui의 Form 컴포넌트를 React Hook Form 및 Zod와 함께 사용하면 타입 매핑 문제가 생길 수 있습니다.
일반적인 오류 메시지는 다음과 같습니다.
Type 'info.${number}.fileName' is not assignable to type '"info" | "info.0" | "info.0.fileName"'
동적 폼 필드의 타입 문제입니다. Zod Schema 타입과 FormField의 name 속성 타입이 일치하지 않습니다.
해결 방법:
Schema 타입을 올바르게 추론하는지 확인합니다.
const formSchema = z.object({
email: z.string().email(),
password: z.string(),
})
type FormValues = z.infer<typeof formSchema> // 이 타입 추론이 반드시 필요함
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
})
FormField의 name 속성은 Schema의 필드 이름과 자동으로 일치합니다.
경험상 주의할 부분: useFieldArray를 사용하는 동적 폼의 타입은 더 복잡합니다. Zod Schema 정의와 TypeScript 타입 추론을 꼼꼼히 확인해야 합니다.
타입 의존성 누락
@types/react나 @types/react-dom을 설치하지 않아 TypeScript 오류가 발생하기도 합니다.
다음과 같은 오류 메시지가 나타날 수 있습니다.
Could not find a declaration file for module 'react'
해결 방법:
npm install -D @types/react @types/react-dom
설치한 뒤 TypeScript 서버를 다시 시작합니다. VSCode에서는 Ctrl+Shift+P를 누르고 TypeScript: Restart TS Server를 입력하면 됩니다.
예방 방법: 새 프로젝트를 시작할 때부터 타입 의존성을 설치하세요. 오류가 발생한 뒤에야 누락을 발견하지 않는 편이 좋습니다.
모범 사례와 예방 방법
여러 문제를 겪으며 몇 가지 예방 방법을 정리했습니다.
설정 관리 원칙
설정 파일 백업:
shadcn/ui를 설치하거나 Tailwind 설정을 수정하기 전에 먼저 백업합니다.
cp tailwind.config.js tailwind.config.js.backup
cp globals.css globals.css.backup
설치 후 차이를 비교하고 설정을 직접 병합합니다.
설정 파일 단일화:
프로젝트에서는 tailwind.config.js 하나와 globals.css 하나만 사용하세요. 설정 파일이 여러 개면 충돌하기 쉽습니다.
빠짐없는 경로 설정:
content 설정에 모든 컴포넌트 디렉터리를 포함해야 합니다.
content: [
'./src/**/*.{ts,tsx}', // 와일드카드로 모든 디렉터리 포함
'./app/**/*.{ts,tsx}',
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
]
의존성 버전 관리
peerDependencies 확인:
새 의존성을 설치하기 전에 peerDependencies를 확인합니다.
npm info <package> peerDependencies
의존성이 React 18을 요구하는데 프로젝트에서 React 19를 사용한다면 호환성을 검토해야 합니다.
타입 의존성 정기 업데이트:
npm update @types/react @types/react-dom
타입 선언과 React 버전을 서로 맞춰 유지합니다.
테스트 전략
설치 직후 테스트:
shadcn/ui 설치가 끝나면 바로 스타일을 테스트합니다.
- 간단한 페이지를 만들고 shadcn/ui 컴포넌트 몇 개를 사용합니다.
- 스타일이 정상적으로 표시되는지 확인합니다.
- 다크 모드 전환을 테스트합니다.
- TypeScript 컴파일 검사를 실행합니다.
프로덕션 환경 테스트:
로컬 개발 환경에서 문제가 없다고 프로덕션도 정상인 것은 아닙니다.
npm run build
npm run preview
빌드한 뒤 미리 보기에서 스타일과 타입이 정상인지 확인합니다.
정리
shadcn/ui의 흔한 문제는 주로 세 영역에 집중됩니다.
- 스타일 충돌: 설정 파일 덮어쓰기, CSS 변수 충돌, 다른 UI 라이브러리와의 공존 문제
- 컴포넌트 렌더링 실패: content 경로 설정 오류, globals.css 경로 문제, import 경로의 대소문자 구분
- TypeScript 타입 오류: variant 속성 타입 누락, React 버전 비호환, 타입 의존성 누락
문제가 발생하면 다음 순서로 확인하세요.
- 먼저 설정 파일(
tailwind.config.js,globals.css)을 확인합니다. - 다음으로 경로 설정(content, import 경로)을 확인합니다.
- 마지막으로 타입 정의(컴포넌트 타입, 의존성 버전)를 확인합니다.
shadcn/ui를 처음 사용한다면 빈 프로젝트에서 전체 과정을 한 번 연습해 보는 것이 좋습니다. 설정 방식과 흔한 문제에 익숙해진 뒤 실제 프로젝트에 적용하세요.
shadcn/ui는 유용하지만 설정이 다소 복잡합니다. 위의 진단 방법을 익혀 두면 문제가 생겨도 당황하지 않고 원인을 찾을 수 있습니다.
FAQ
shadcn/ui를 설치한 뒤 스타일이 모두 사라지는 이유는 무엇인가요?
• tailwind.config.js의 plugins 배열이 온전한지
• globals.css 경로가 올바른지
• CSS 변수가 모두 정의되어 있는지
해결 방법: 설치 전에 설정 파일을 백업하고, 설치 후 차이를 비교해 사라진 설정을 직접 복원하세요.
다크 모드로 전환한 뒤 컴포넌트 스타일이 잘못 표시되는 이유는 무엇인가요?
• .dark 클래스가 반드시 있어야 합니다.
• 모든 테마 변수를 다시 정의해야 합니다.
• Tailwind v4에서는 @theme inline으로 변수를 매핑해야 합니다.
shadcn/ui와 MUI를 함께 사용할 수 있나요?
• prefix: 'tw-'를 설정해 모든 Tailwind 클래스에 접두사를 붙입니다.
• 새 컴포넌트에는 shadcn/ui를 사용하고 기존 컴포넌트에는 MUI를 유지합니다.
• 한 번에 전부 바꾸지 말고 단계적으로 마이그레이션합니다.
Tailwind 스타일에 영향을 줄 수 있으므로 Preflight를 비활성화하는 방법은 권장하지 않습니다.
Button의 variant 속성에서 TypeScript 오류가 발생하면 어떻게 해야 하나요?
• VariantProps<typeof buttonVariants>를 반드시 상속해야 합니다.
• 컴포넌트 파일에서 타입을 온전히 내보내는지 확인합니다.
• @types/react와 @types/react-dom을 설치합니다.
타입 정의가 누락되었다면 npx shadcn@latest add button을 다시 실행해 컴포넌트를 설치하세요.
React 19에서 shadcn/ui를 사용할 수 있나요?
• 설치할 때 --legacy-peer-deps 또는 --force를 사용합니다.
• 또는 package.json의 overrides에서 react-is 버전을 지정합니다.
• 새 프로젝트라면 먼저 React 18을 사용하고, 의존성이 업데이트된 뒤 업그레이드하는 편이 안전합니다.
로컬에서는 정상인 컴포넌트가 프로덕션에서 모듈을 찾지 못하는 이유는 무엇인가요?
• shadcn/ui 컴포넌트 파일명은 모두 소문자입니다(button.tsx).
• import 경로가 파일명과 일치해야 합니다(@/components/ui/button).
• Windows는 대소문자를 구분하지 않지만 Linux/Mac은 구분하므로 로컬에서는 정상이어도 프로덕션에서 오류가 날 수 있습니다.
모든 import 문을 검사해 경로가 정확히 일치하는지 확인하세요.
3분 읽기 · 게시일: 2026년 4월 2일 · 수정일: 2026년 9월 4일



댓글
GitHub로 로그인하여 댓글을 남기세요