shadcn/ui 설치와 CSS 변수로 테마 설정하는 방법

처음 shadcn/ui를 사용했을 때는 ‘npm 패키지가 아니다’라는 방식이 낯설었습니다. 코드를 프로젝트에 복사한다고요? 너무 원시적인 방법처럼 들렸습니다.
하지만 몇 번 사용해 보니 바로 그 점이 shadcn/ui의 강점이라는 사실을 알게 됐습니다. 모든 컴포넌트의 소스 코드를 직접 소유하므로 원하는 대로 수정할 수 있고, 버전 충돌이나 컴포넌트 라이브러리의 디자인 제약도 걱정할 필요가 없습니다.
여기서는 shadcn/ui 설치와 설정, 테마 맞춤 설정을 살펴봅니다. 특히 CSS 변수로 브랜드 디자인 시스템을 구현하는 방법에 집중합니다. 기본 설정은 5분 안에 마치고, 한 시간 정도 더 투자하면 원하는 모습으로 테마를 조정할 수 있습니다.
1. 빠른 설치: 두 가지 방법
방법 1: CLI로 한 번에 초기화하기(권장)
새 프로젝트에서는 다음 명령을 바로 실행합니다.
npx shadcn@latest init
실행하면 여러 가지를 묻습니다. TypeScript와 JavaScript 중 무엇을 사용할지, 어떤 스타일과 기본 테마를 선택할지 정하면 됩니다. 전체 과정이 대화형이므로 안내에 따라 선택하면 됩니다.
설치가 끝나면 프로젝트에 다음 파일이 추가됩니다.
components.json- 설정 파일lib/utils.ts- 유틸리티 함수components/ui/- 컴포넌트 디렉터리
컴포넌트를 추가하는 방법도 간단합니다. 버튼이 필요하다면 다음 명령을 실행합니다.
npx shadcn@latest add button
컴포넌트 코드가 components/ui/button.tsx에 자동으로 복사되므로 바로 import해서 사용할 수 있습니다.
주의할 점이 있습니다. 프로젝트 개발이 어느 정도 진행됐다면 tailwind.config.js와 globals.css에 이미 많은 설정이 들어 있을 수 있습니다. shadcn의 init 명령은 이 파일을 덮어쓸 수 있으므로 프로젝트를 시작할 때 설치하는 편이 좋습니다.
어떤 블로거의 조언이 꽤 정확했습니다. shadcn/ui를 프로젝트의 ‘첫 번째 의존성 묶음’으로 생각하고 나중으로 미루지 말라는 이야기였습니다. 실제로 겪어 보면 실감하게 됩니다.
방법 2: 수동 설치(기존 프로젝트에 적합)
프로젝트 구조가 이미 자리 잡아 CLI가 설정을 덮어쓸 위험이 크다면 수동으로 설치합니다.
단계별로 진행해 보겠습니다.
1단계: Tailwind CSS 설치 확인
shadcn 컴포넌트는 모두 Tailwind로 작성돼 있습니다. 아직 설치하지 않았다면 먼저 Tailwind를 설치하세요. 공식 문서의 안내가 잘 정리돼 있습니다.
2단계: 의존성 설치
npm install class-variance-authority clsx tailwind-merge
npm install lucide-react
class-variance-authority(줄여서 CVA)는 유용한 도구입니다. 뒤에서 컴포넌트 변형을 만들 때 사용합니다.
3단계: 경로 별칭 설정
tsconfig.json에 다음 설정을 추가합니다.
{
"compilerOptions": {
"paths": {
"@/*": ["./*"]
}
}
}
이제 ../../../를 길게 쓰지 않고 @/components/ui/button처럼 컴포넌트를 가져올 수 있습니다.
4단계: components.json 생성
프로젝트 루트에 다음 파일을 만듭니다.
{
"style": "new-york",
"rsc": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
cssVariables: true가 중요합니다. Tailwind utility class 대신 CSS 변수로 테마를 구성한다는 의미입니다.
5단계: 스타일 추가
globals.css에 shadcn의 기본 스타일을 추가합니다. 테마를 설명하는 다음 절에서 자세히 다룹니다.
2. 테마 시스템 이해하기: CSS 변수 활용법
shadcn/ui의 테마 시스템은 간단한 규칙을 따릅니다. 각 색상에는 background와 foreground 변수가 한 쌍으로 존재합니다.
무슨 뜻일까요? 다음 예를 보겠습니다.
:root {
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
}
--primary는 버튼의 배경색이고, --primary-foreground는 버튼 위의 글자색입니다. 이렇게 짝을 지으면 변수 하나만 바꿔도 관련된 모든 컴포넌트의 색상이 함께 변경됩니다.
CSS 변수 목록
shadcn/ui는 기본적으로 다음 변수를 정의합니다.
| 변수 | 용도 |
|---|---|
--background | 페이지 배경색 |
--foreground | 페이지 글자색 |
--card | 카드 배경 |
--card-foreground | 카드 글자색 |
--popover | 팝오버 배경 |
--popover-foreground | 팝오버 글자색 |
--primary | 기본 색상(버튼, 링크) |
--primary-foreground | 기본 색상 위의 글자색 |
--secondary | 보조 색상 |
--secondary-foreground | 보조 색상 위의 글자색 |
--muted | 차분한 배경색 |
--muted-foreground | 차분한 글자색 |
--accent | 강조 색상 |
--accent-foreground | 강조 색상 위의 글자색 |
--destructive | 위험한 작업(삭제 버튼) |
--destructive-foreground | 위험한 작업 색상 위의 글자색 |
--border | 테두리 |
--input | 입력 필드 |
--ring | 포커스 링 |
변수가 많아 보이지만 background/foreground 짝만 이해하면 쉽게 기억할 수 있습니다.
HSL 형식의 비밀
shadcn의 색상 값이 표준 HSL 형식과 다르다는 점을 눈치챘을 수도 있습니다.
/* ❌ 표준 HSL */
--primary: hsl(222.2, 47.4%, 11.2%);
/* ✅ shadcn 형식 */
--primary: 222.2 47.4% 11.2%;
왜 이런 ‘괄호 없는’ 형식으로 작성할까요?
Tailwind가 투명도 수정자를 지원하기 때문입니다. 예를 들어 bg-primary/50은 기본 색상에 50% 투명도를 적용합니다. 변수가 완전한 hsl() 형식이면 이 기능을 사용할 수 없습니다.
값만 작성하면 Tailwind가 hsl()과 투명도를 자동으로 더합니다. 영리한 설계입니다.
3. 브랜드 테마 맞춤 설정하기
방법 1: CSS 변수 직접 수정
가장 간단한 방법은 globals.css를 열고 :root 부분의 색상 값을 바꾸는 것입니다.
예를 들어 기본 색상을 파란색에서 보라색으로 변경하려면 다음과 같이 작성합니다.
:root {
--primary: 270 60% 60%;
--primary-foreground: 0 0% 100%;
}
.dark {
--primary: 270 60% 70%;
--primary-foreground: 0 0% 0%;
}
저장하면 bg-primary를 사용하는 모든 버튼과 링크가 보라색으로 바뀝니다.
방법 2: OKLCH 색 공간 사용(Tailwind v4)
Tailwind v4를 사용한다면 OKLCH 색 공간을 고려할 수 있습니다. HSL보다 사람의 색상 인식에 가깝고, 더 균일한 색조 단계를 만들 수 있습니다.
:root {
--primary: oklch(0.6 0.2 270);
--primary-foreground: oklch(0.98 0 0);
}
여기서 oklch(0.6 0.2 270)의 세 매개변수는 다음과 같습니다.
0.6- 밝기(0~1)0.2- 채도(약 0~0.4)270- 색상 각도(0~360)
방법 3: 온라인 도구로 생성
직접 색상을 조합하기 번거롭다면 온라인 도구를 사용할 수 있습니다.
Shadcn Theme Generator를 추천합니다.
기본 색상을 선택하면 도구가 라이트와 다크 테마를 포함한 전체 CSS 변수 세트를 자동으로 생성합니다. 결과를 복사해 globals.css에 붙여 넣으면 됩니다.
4. 다크 모드 설정
next-themes로 테마 전환 구현
shadcn/ui 자체에는 테마 전환 기능이 없지만 next-themes 라이브러리로 구현할 수 있습니다.
먼저 설치합니다.
npm install next-themes
그다음 layout.tsx에 다음과 같이 설정합니다.
import { ThemeProvider } from "next-themes"
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="ko" suppressHydrationWarning>
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
>
{children}
</ThemeProvider>
</body>
</html>
)
}
핵심은 다음과 같습니다.
- 하이드레이션 경고를 피하려면
suppressHydrationWarning을 반드시 추가합니다. attribute="class"는 class 이름으로 테마를 전환한다는 뜻입니다.defaultTheme="system"은 시스템 설정을 기본값으로 사용한다는 뜻입니다.enableSystem은 시스템 테마 감지를 활성화합니다.
테마 전환 버튼 만들기
useTheme hook으로 현재 테마와 테마 설정 함수를 가져옵니다.
import { useTheme } from "next-themes"
import { Moon, Sun } from "lucide-react"
export function ThemeToggle() {
const { theme, setTheme } = useTheme()
return (
<button
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
className="p-2 rounded-md hover:bg-accent"
>
{theme === "dark" ? <Sun size={20} /> : <Moon size={20} />}
</button>
)
}
다크 모드를 기본값으로 설정
사이트를 기본적으로 다크 모드로 표시하려면 두 가지 방법이 있습니다.
방법 1: dark class 하드코딩
<html lang="ko" className="dark">
이렇게 작성하면 테마가 다크 모드로 고정돼 전환할 수 없습니다.
방법 2: 기본 테마 설정
<ThemeProvider
attribute="class"
defaultTheme="dark" // 다크 모드를 기본값으로 설정
enableSystem={false} // 시스템 감지 비활성화
>
이 방법은 사용자가 수동으로 전환할 수 있지만 초기 상태는 다크 모드입니다.
5. 고급 맞춤 설정: 컴포넌트 변형
CVA로 사용자 정의 변형 만들기
버튼에 ‘위험’, ‘성공’, ‘그라데이션’ 같은 여러 스타일을 추가해야 할 때가 있습니다. CVA를 사용하면 이런 변형을 쉽게 정의할 수 있습니다.
import { cva, type VariantProps } from "class-variance-authority"
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors",
{
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",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
컴포넌트에서는 다음과 같이 사용합니다.
<button className={buttonVariants({ variant: "destructive", size: "lg" })}>
삭제
</button>
shadcn 컴포넌트 소스 코드를 직접 수정하지 않기
이는 중요한 모범 사례입니다.
shadcn 컴포넌트 코드는 프로젝트에 있으므로 원하는 대로 수정할 수 있습니다. 하지만 원본 파일은 직접 수정하지 않는 편이 좋습니다. 대신 래퍼 컴포넌트를 만드세요.
왜일까요? shadcn은 컴포넌트를 자주 업데이트합니다. 원본 파일을 수정하면 업데이트할 때마다 수동으로 병합해야 하므로 번거롭습니다.
더 나은 방법은 다음과 같습니다.
// components/brand-button.tsx
import { Button } from "@/components/ui/button"
import { cva } from "class-variance-authority"
const brandButtonVariants = cva("...", {
variants: {
brand: {
primary: "bg-brand-primary text-white",
secondary: "bg-brand-secondary text-black",
},
},
})
export function BrandButton({ brand, ...props }) {
return <Button className={brandButtonVariants({ brand })} {...props} />
}
원본 Button 컴포넌트는 그대로 두고 BrandButton을 별도로 만들었습니다. 이렇게 하면 나중에 shadcn을 업데이트해도 맞춤 설정에 영향을 주지 않습니다.
6. 자주 발생하는 문제와 해결 방법
문제 1: 설치 후 스타일이 적용되지 않음
다음 항목을 확인하세요.
globals.css를layout.tsx에서 import했나요?- Tailwind의
content설정에components/**/*가 포함돼 있나요? components.json의 경로 설정이 올바른가요?
문제 2: 테마 전환 시 화면 깜빡임
대개 하이드레이션 불일치가 원인입니다. 다음을 확인하세요.
<html>태그에suppressHydrationWarning을 추가합니다.- ThemeProvider가 전체 앱을 감싸야 합니다.
- 서버 렌더링 중에는 theme 값을 읽지 않습니다(undefined가 됩니다).
문제 3: CSS 변수가 적용되지 않음
가능한 원인은 다음과 같습니다.
- 변수 이름을 잘못 작성했습니다(
--primaryForeground가 아니라--primary-foreground입니다). - 대응하는
.dark스타일이 없습니다. - 변수 값 형식이 잘못됐습니다(괄호 없는 HSL 또는 OKLCH를 사용해야 합니다).
문제 4: 컴포넌트 스타일 충돌
프로젝트에 이미 스타일 시스템이 있다면 shadcn의 스타일과 충돌할 수 있습니다. 해결 방법은 다음과 같습니다.
- shadcn 컴포넌트에 namespace를 추가합니다(예:
shadcn-button). - Tailwind의 layer 우선순위를 조정합니다.
- CVA로 자체 변형을 만들고 기본 스타일에 의존하지 않습니다.
7. 정리
shadcn/ui의 설치와 설정은 어렵지 않습니다. 핵심은 ‘의존성 패키지 대신 코드를 복사한다’는 설계 철학을 이해하는 것입니다. 이 방식은 모든 부분을 제어할 수 있다는 장점이 있지만, 프로젝트마다 컴포넌트 코드 세트를 직접 관리해야 한다는 단점도 있습니다.
테마 맞춤 설정에서는 CSS 변수 시스템이 깔끔하게 설계돼 있습니다. 변수 몇 개만 바꾸면 전체 애플리케이션의 색상 구성이 함께 변경됩니다. next-themes와 결합하면 라이트 모드와 다크 모드 전환도 몇 줄의 코드로 구현할 수 있습니다.
마지막으로 몇 가지를 권합니다.
- 새 프로젝트에서는 CLI 초기화를 우선 사용하세요. 수동 설정 작업을 줄일 수 있습니다.
- primary, secondary 같은 의미 기반 색상 변수를 사용하세요. 구체적인 색상 이름은 피하는 편이 좋습니다.
- 라이트 모드와 다크 모드에서 대비를 모두 테스트하세요. 읽기 쉬운지 확인해야 합니다.
- 원본 소스 코드를 수정하는 대신 래퍼 컴포넌트를 만드세요. 향후 업데이트가 쉬워집니다.
다음에 테마가 있는 UI를 빠르게 구성해야 한다면 shadcn/ui를 사용해 보세요. 복사하고 붙여 넣는 방식의 편리함은 직접 써 보면 알 수 있습니다.
참고 자료
- shadcn/ui 공식 문서 - Installation
- shadcn/ui 공식 문서 - Theming
- shadcn/ui 공식 문서 - Dark Mode
- Generate Custom shadcn/ui Themes
- Theming in shadcn UI: CSS Variables
shadcn/ui 설치와 테마 맞춤 설정
shadcn/ui를 처음부터 설치하고 테마 시스템을 구성해 브랜드 디자인을 구현합니다
⏱️ Estimated time: 30 min
- 1
Step 1: CLI 빠른 초기화
새 프로젝트에서 설치 명령을 실행합니다.
• npx shadcn@latest init
• TypeScript, New York 스타일, 기본 테마 선택
• CLI가 설정을 마칠 때까지 대기 - 2
Step 2: 브랜드 기본 색상 변경
globals.css의 CSS 변수를 편집합니다.
• app/globals.css 열기
• :root 아래의 --primary 변수 찾기
• 브랜드 색상으로 변경(HSL 또는 OKLCH 형식)
• 대비를 확보하도록 --primary-foreground도 함께 변경 - 3
Step 3: 다크 모드 설정
next-themes를 설치하고 설정합니다.
• npm install next-themes
• layout.tsx에 ThemeProvider 추가
• 하이드레이션 경고를 방지하도록 suppressHydrationWarning 설정
• 테마 전환 컴포넌트 생성 - 4
Step 4: 컴포넌트 변형 만들기
CVA로 사용자 정의 스타일을 정의합니다.
• class-variance-authority 설치
• variants와 defaultVariants 정의
• 컴포넌트에서 buttonVariants() 적용
• 원본 shadcn 컴포넌트 유지
FAQ
shadcn/ui는 기존 UI 컴포넌트 라이브러리와 무엇이 다른가요?
새 프로젝트를 초기화할 때 shadcn/ui를 설치하는 것이 좋은 이유는 무엇인가요?
CSS 변수는 왜 표준 HSL 대신 괄호 없는 값 형식으로 작성하나요?
브랜드 기본 색상은 어떻게 변경하나요?
다크 모드 전환 시 화면이 깜빡이는 이유는 무엇인가요?
shadcn 컴포넌트 소스 코드를 직접 수정해야 하나요?
3분 읽기 · 게시일: 2026년 3월 26일 · 수정일: 2026년 9월 4일
Tailwind & shadcn/ui 실전
검색으로 들어왔다면 같은 시리즈의 이전 글이나 다음 글로 이동하는 것이 가장 빠릅니다.
이전
shadcn/ui란? MUI·Chakra 컴포넌트 라이브러리 비교 및 선택 가이드
shadcn/ui, Material-UI, Chakra UI, Ant Design을 번들 크기, 커스터마이징 유연성, 개발 경험, 접근성 등 7가지 관점에서 심층 비교해 최적의 선택을 돕습니다
14편 중 3편
다음
shadcn/ui로 관리자 페이지 뼈대 만들기: Sidebar + Layout 모범 사례
shadcn/ui Sidebar와 Next.js Layout을 통합하는 모범 사례를 알아봅니다. 컴포넌트 아키텍처와 반응형 디자인부터 권한 제어까지, 확장 가능한 관리자 페이지 뼈대를 완전한 코드 예제와 함께 단계별로 구축합니다.
14편 중 5편



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