brandonwie.dev
EN / KR
On this page
general generalreactshadcn-uitailwindvitefrontend

shadcn/ui 수동 설정 (Vite + Tailwind)

Vite + React + TypeScript + Tailwind CSS 프로젝트에서 shadcn CLI 없이 shadcn/ui 컴포넌트 프리미티브를 수동으로 설정하는 방법이에요.

Updated March 22, 2026 4 min read

React 대시보드 프로젝트에 컴포넌트 라이브러리가 필요했어요. 요구사항이 꽤 구체적이었어요. 처음부터 다크 테마, 스타일링에 대한 완전한 제어, 비대한 의존성 트리 없을 것. Material UI, Chakra, Ant Design을 살펴봤어요. 전부 원하지 않는 의견과 필요 없는 CSS-in-JS 오버헤드를 갖고 있었어요.

그러다 shadcn/ui를 발견했어요. 전통적인 의미의 컴포넌트 라이브러리가 아니에요. 의존성으로 설치하는 게 아니라 컴포넌트를 프로젝트에 복사해서 완전히 소유하는 방식이에요. 접근성을 위해 Radix UI 프리미티브로 구축되고, 유연성을 위해 Tailwind CSS로 스타일링돼요. CLI를 건너뛰고 수동으로 설정하면서 각 조각을 직접 파악했어요.

CLI 대신 수동 설정을 선택한 이유

shadcn/ui 문서는 npx shadcn-ui init을 실행하라고 권장해요. 작동하지만 블랙박스예요. components.json 설정 파일을 생성하고, 필요 없을 수 있는 의존성을 설치하고, 정해진 폴더 구조를 만들어요. 완전한 제어를 원하는 프로젝트에서는 수동 설정이 더 나았어요.

요소CLI (npx shadcn-ui init)수동
제어정해진 구조완전한 제어
의존성전부 설치필요한 것만
설정components.json 생성불필요
학습블랙박스각 요소를 이해

수동 접근법은 더 적은 의존성을 설치하고 각 조각이 무슨 역할을 하는지 이해하도록 강제해요. 뭔가 깨졌을 때 어디를 봐야 하는지도 자연스럽게 보여요.

shadcn/ui가 실제로 무엇인가

shadcn/ui는 Radix UI 프리미티브 위에 구축된 copy-paste 컴포넌트 모음이에요. Radix가 어려운 접근성 작업을 다 처리해줘요 — 키보드 내비게이션, 포커스 관리, ARIA 속성. shadcn/ui는 이 프리미티브를 Tailwind 스타일로 감싸면서 일관된 API를 제공해요.

핵심 의존성은 작고 조합 가능해요:

패키지용도
class-variance-authority컴포넌트 variant 정의 (cva)
clsx조건부 className 결합
tailwind-merge충돌하는 Tailwind 클래스 중복 제거
@radix-ui/react-slot다형성 asChild prop 지원
tailwindcss-animateTailwind용 애니메이션 유틸리티

단계별 설정

1. 의존성 설치

npm install class-variance-authority clsx tailwind-merge
npm install @radix-ui/react-slot
npm install tailwindcss-animate

명령어 세 개, 패키지 다섯 개. 시작하는 데 필요한 전부예요.

2. cn() 유틸리티 생성

모든 shadcn/ui 컴포넌트의 기반이에요. clsx(조건부 클래스 결합)와 tailwind-merge(클래스 중복 제거)를 결합해요.

// src/lib/utils.ts
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

두 라이브러리가 다 필요한 이유는? clsxclsx('base', isActive && 'active') 같은 조건부 클래스를 처리해요. tailwind-mergep-4 p-2 같은 충돌을 p-2로 해소해요 (마지막이 승리). 둘을 합치면 className 조합이 예측 가능해져요.

3. Tailwind 플러그인 추가

// tailwind.config.js
plugins: [require("tailwindcss-animate")];

shadcn/ui 컴포넌트가 트랜지션에 사용하는 animate-in, animate-out, fade-in, slide-in-from-top 같은 애니메이션 유틸리티를 추가해요.

4. Path Alias 설정

선택 사항이지만 강력히 권장해요. 없으면 import가 금방 장황해져요:

// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": { "@/*": ["./src/*"] }
  }
}

// vite.config.ts
import path from 'path';
export default defineConfig({
  resolve: {
    alias: { '@': path.resolve(__dirname, './src') }
  }
});

이제 상대 경로를 탐색하는 대신 import { cn } from '@/lib/utils'로 쓸 수 있어요.

5. 테마용 CSS 변수 추가

shadcn/ui는 테마에 CSS 변수를 사용해요. 글로벌 스타일시트에 정의하세요:

:root {
  --background: #1a1a1a;
  --foreground: #e5e5e5;
  --border: #404040;
  /* ... 디자인 토큰들 ... */
}

컴포넌트는 bg-background, text-foreground, border-border 같은 Tailwind 클래스를 통해 이 변수들을 참조해요.

Variant로 컴포넌트 만들기

설정이 완료되면 variant 정의를 위해 cva(class variance authority)를 사용해서 컴포넌트를 만들어요:

import { cn } from "@/lib/utils";
import { cva, type VariantProps } from "class-variance-authority";

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-md text-sm",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground",
        outline: "border border-border bg-transparent",
      },
      size: {
        default: "h-10 px-4 py-2",
        sm: "h-9 px-3",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  },
);

cva 함수는 기본 클래스 세트, variant 옵션, 기본값을 정의해요. 컴포넌트를 사용할 때 variant prop을 전달하면 cva가 최종 className을 계산해요. cn() 유틸리티가 모든 걸 병합하면서 충돌을 중복 제거해요.

왜 이 방법이 효과적인가

수동 설정이 효과적인 이유는 shadcn/ui가 분해되도록 설계됐기 때문이에요. 숨겨진 런타임도 없고, 앱을 감싸야 하는 context provider도 없고, 기존 스타일과 충돌하는 글로벌 CSS도 없어요. 각 컴포넌트는 cn()과 Radix 프리미티브를 import하는 자기 완결적인 파일이에요. 필요한 걸 복사하고, 커스터마이징하고, 넘어가면 돼요.

cn() + cva 패턴은 Tailwind의 어려운 부분인 className 조합을 처리해줘요. 이게 없으면 조건부 클래스와 Tailwind 오버라이드를 결합하면 예측할 수 없는 결과가 나와요. 이게 있으면 마지막 클래스가 항상 이기고 조건문이 깔끔해져요.

실전 팁

컴포넌트 라이브러리에 대한 완전한 제어를 원할 때 이 수동 설정을 사용하세요. 핵심 패키지 세 개(class-variance-authority, clsx, tailwind-merge)로 시작하고, cn() 유틸리티를 만들고, 컴포넌트별로 필요한 Radix 프리미티브를 추가하세요.

학습 프로젝트나 비표준 구조가 필요할 때는 CLI를 건너뛰세요. 기본 구조가 맞는 경우 빠른 프로토타이핑에는 CLI를 사용하세요.

핵심 인사이트: shadcn/ui는 의존하는 라이브러리가 아니에요. 소유하는 패턴의 모음이에요. cn() 유틸리티와 cva variant 시스템이 진짜 핵심 — shadcn/ui의 구체적인 컴포넌트 없이도 사용할 수 있어요.

Comments

enko