본문 바로가기
✨FRONTEND/📍React

shadcn/ui: 도입부터 커스텀까지

by 짱돌보리 2026. 2. 6.
728x90

🎨 shadcn/ui: 도입부터 커스텀까지

프론트엔드 개발을 하다 보면

“디자인은 통일하고 싶은데, 커스텀은 자유로웠으면 좋겠고,

라이브러리는 너무 무겁지 않았으면…” 같은 고민을 하게 된다.

 

이럴 때 딱 맞는 선택지가 shadcn/ui다.

1. shadcn/ui란?

https://ui.shadcn.com/docs/installation

 

Installation

How to install dependencies and structure your app.

ui.shadcn.com

 

shadcn/ui는 단순한 컴포넌트 라이브러리가 아니다.

필요한 컴포넌트의 소스 코드를 내 프로젝트로 직접 가져오는 방식의 UI 툴킷이다.

 

즉,

👉 node_modules에 숨겨진 컴포넌트를 쓰는 게 아니라

👉 내 코드로 복사된 컴포넌트를 직접 수정하며 사용하는 구조

🔍 핵심 특징

  • 컴포넌트 소유권 = 내 프로젝트
  • 디자인 시스템을 강요하지 않음
  • 필요 없는 의존성 최소화

✅ 장점

  • 스타일 커스텀 100% 자유
  • 코드 흐름 파악이 쉬움
  • 라이브러리 업데이트에 덜 휘둘림

🧱 기반 기술

  • Tailwind CSS → 빠르고 일관된 스타일링
  • Radix UI → 접근성(A11y)이 검증된 Headless 컴포넌트

💡 “라이브러리를 쓰는 느낌보다,

잘 만들어진 기본 컴포넌트를 상속받는 느낌에 가깝다.”


2. 설치 및 초기화 (pnpm 기준)

shadcn/ui는 CLI를 통해 프로젝트 환경을 먼저 세팅한다.

1) 초기화

터미널에서 아래 명령어를 실행한다.

pnpm dlx shadcn@latest init

 

초기화 과정에서 다음과 같은 설정을 선택하게 된다.

  • 사용 중인 프레임워크 (Next.js / React 등)
  • TypeScript 사용 여부
  • Tailwind 설정 경로
  • 컴포넌트 생성 위치 (src/components/ui 등)

👉 이 단계에서 프로젝트 구조에 맞게 커스터마이징할 수 있다는 점이 shadcn의 큰 장점이다.


3. 컴포넌트 추가 및 사용법

1) 필요한 것만 골라 담기

shadcn/ui는 모든 컴포넌트를 한 번에 설치하지 않는다.

필요한 컴포넌트만 그때그때 추가한다.

pnpm dlx shadcn@latest add button checkbox dialog sonner

📁 생성되는 구조

설치가 완료되면 아래 경로에 컴포넌트 소스가 생성된다.

👉 이 파일들은 전부 수정 가능

👉 Tailwind 클래스 추가, props 확장, 디자인 변경 전부 자유

 

마지막으로 layout.tsx에 넣어주면 끝이다! 

 

<html lang="ko">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <AuthProvider>
          <Header />
          <AuthGuard>
            <div className="min-h-screen">{children}</div>
          </AuthGuard>
          <Footer />
          
          <Toaster position="top-right" /> // 여기!!!!
          
        </AuthProvider>
      </body>
    </html>

 

 

아래는 예시)
토스트 관련 컴포넌트인데 내가 원하는 style을 적용했음!!

 

'use client'

import {
  CircleCheckIcon,
  InfoIcon,
  Loader2Icon,
  OctagonXIcon,
  TriangleAlertIcon,
} from 'lucide-react'
import { useTheme } from 'next-themes'
import { Toaster as Sonner, type ToasterProps } from 'sonner'

const Toaster = ({ ...props }: ToasterProps) => {
  const { theme = 'system' } = useTheme()

  return (
    <Sonner
      theme={theme as ToasterProps['theme']}
      className="toaster group"
      icons={{
        success: <CircleCheckIcon className="size-4 text-sky-400" />,
        info: <InfoIcon className="size-4 text-emerald-400" />,
        warning: <TriangleAlertIcon className="size-4 text-amber-400" />,
        error: <OctagonXIcon className="size-4 text-rose-400" />,
        loading: (
          <Loader2Icon className="size-4 animate-spin text-indigo-300" />
        ),
      }}
      toastOptions={{
        // style에 정의된 변수를 강제로 사용하도록 설정
        className: `
          !backdrop-blur-md 
          !shadow-2xl 
          !font-bold 
          data-[type=error]:!bg-[var(--error-bg)] 
          data-[type=error]:!text-[var(--error-text)] 
          data-[type=error]:!border-[var(--error-border)]
          data-[type=success]:!bg-[var(--success-bg)] 
          data-[type=success]:!text-[var(--success-text)] 
          data-[type=success]:!border-[var(--success-border)]
          data-[type=warning]:!bg-[var(--warning-bg)] 
          data-[type=warning]:!text-[var(--warning-text)] 
          data-[type=warning]:!border-[var(--warning-border)]
          data-[type=info]:!bg-[var(--info-bg)] 
          data-[type=info]:!text-[var(--info-text)] 
          data-[type=info]:!border-[var(--info-border)]
        `,
      }}
      style={
        {
          '--success-bg': 'rgba(56, 189, 248, 0.1)',
          '--success-text': '#38bdf8',
          '--success-border': 'rgba(56, 189, 248, 0.35)',

          '--error-bg': 'rgba(244, 63, 94, 0.1)',
          '--error-text': '#fb7185',
          '--error-border': 'rgba(244, 63, 94, 0.35)',

          '--warning-bg': 'rgba(245, 158, 11, 0.1)',
          '--warning-text': '#fbbf24',
          '--warning-border': 'rgba(245, 158, 11, 0.35)',

          '--info-bg': 'rgba(16, 185, 129, 0.1)',
          '--info-text': '#34d399',
          '--info-border': 'rgba(16, 185, 129, 0.35)',

          '--border-radius': '1rem',
        } as React.CSSProperties
      }
      {...props}
    />
  )
}

export { Toaster }

 


4. shadcn/ui를 쓰면서 느낀 점

  • 디자인 시스템을 강요하지 않고
  • 구조만 제공하고
  • 결정권은 개발자에게 남겨둔다!