Radix UI와 Tailwind CSS를 기반으로 구축된 재사용 가능한 컴포넌트 모음입니다.
다른 라이브러리와 가장 큰 차이점은 복사-붙여넣기 방식이라는 점인데요
즉, npm 패키지를 설치해 사용하는 라이브러리가 아니라 필요한 컴포넌트의 코드를 복사해서 가져와 프로젝트에 맞게 수정하여 사용하는 방식입니다.
이러한 접근 방식은 개발자에게 높은 유연성을 제공합니다! 컴포넌트의 모든 부분을 수정 및 확장할 수 있으며, 프로젝트의 디자인 시스템과 완벽하게 통합된 UI를 구현할 수 있습니다.
1️⃣ Headless UI 기반
shadcn/ui는 Radix UI를 기반으로 한 Headless 컴포넌트 구조를 사용합니다.
여기서 Headless UI란, 스타일은 최소화하고 로직만 제공하는 구조를 의미합니다.
덕분에 개발자는
기능은 안정적으로 보장하면서도, UI 표현은 완전히 커스터마이징할 수 있는 구조입니다.
2️⃣ Tailwind CSS 기반 스타일링
모든 컴포넌트가 tailwind 클래스 기반으로 스타일링되어 있어, 유틸리티 퍼스트 패러다임을 그대로 따릅니다.
따라서 유틸리티 퍼스트 스타일링의 장점 - 가벼운 번들 크기, 뛰어난 런타임 성능, 서버 사이드 렌더링 환경에서 안정적인 점 등을 포함하고 있습니다.
또한 tailwind와의 호환성이 좋습니다.
3️⃣ 쉬운 커스터마이징
Radix UI와 Tailwind CSS 조합 덕분에 일관된 디자인 시스템 구축이 용이합니다.
또한 컴포넌트 코드를 직접 수정할 수 있어 프로젝트 맞춤형 설계가 가능합니다.
핵심은 높은 호환성과 확장성!
shadcn/ui가 Tailwind CSS를 전제로 설계된 컴포넌트 시스템이기 때문입니다.
shadcn/ui의 컴포넌트는 Tailwind 클래스 기반으로 구성되어 있어 별도의 스타일 규칙 없이도 바로 적용할 수 있고, 필요에 따라 생성된 코드를 직접 수정해 프로젝트에 맞게 자유롭게 커스터마이징할 수 있습니다.
즉, Tailwind CSS의 유연한 스타일링 방식 위에 shadcn/ui가 구조와 동작이 정리된 컴포넌트를 얹는 형태이기 때문에 호환성이 좋고, 확장과 유지보수가 쉬운 조합이 됩니다.
// Using Vite
pnpm install -D tailwindcss @tailwindcss/vite
shadcn/ui는 CLI를 통해 프로젝트에 필요한 설정과 컴포넌트를 추가합니다.
pnpm dlx shadcn@latest init
설치 과정에서 컴포넌트 경로, 스타일 방식, import alias 등을 설정하며 선택한 옵션에 따라 components.json과 기본 구조가 생성됩니다.
globals.css 에 테마 및 디자인 토큰(CSS 변수) 설정 추가libs/utils.ts 생성 및 clsx, twMerge 기반의 cn 유틸함수 추가📍 components.json 파일 알아보기
{
"$schema": "https://ui.shadcn.com/schema.json",
// 컴포넌트 기본 스타일 프리셋
// 초기화 시 선택하며, 이후 변경 불가
"style": "new-york",
// React Server Components 사용 여부
// true일 경우, 필요한 컴포넌트에 "use client"를 자동으로 추가
"rsc": true,
// 타입스크립트 또는 자바스크립트 컴포넌트 선택 여부
"tsx": true,
// Tailwind CSS 관련 설정
"tailwind": {
// tailwind.config 파일 경로
// Tailwind v4에서는 비워두는 것이 일반적
"config": "",
// Tailwind가 import된 전역 CSS 파일 경로
"css": "app/globals.css",
// 컴포넌트 기본 색상 팔레트 (gray / neutral / slate / stone / zinc 등)
// 초기화 이후 변경 불가
"baseColor": "zinc",
// CSS 변수 기반 테마 사용 여부
"cssVariables": true,
// Tailwind 클래스 prefix
// ex) "tw-"로 지정하면 모든 유틸리티 클래스가 tw- 접두사로 생성됨
"prefix": ""
},
// 경로별 alias 설정 — CLI가 컴포넌트 생성 시 위치/import 경로로 사용함
"aliases": {
"components": "@/components",
"ui": "@/components/ui",
"utils": "@/lib/utils",
"lib": "@/lib",
"hooks": "@/hooks"
},
// 기본 아이콘 라이브러리
"iconLibrary": "lucide"
}
필요한 컴포넌트만 선택적으로 추가할 수 있습니다.
pnpm dlx shadcn@latest add 컴포넌트명
→ 명령어 뒤에 원하는 컴포넌트 작성
예를 들어, 버튼 컴포넌트를 생성할 경우
pnpm dlx shadcn@latest add button
이 명령어를 실행하면 다음과 같은 Button 컴포넌트의 코드가 프로젝트 내부(ex. src/components/ui/)에 직접 생성됩니다.
// src/components/ui/button.tsx
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot : "button"
return (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }
Slot : Radix UI의 Slot 컴포넌트로, asChild 패턴을 통해 버튼을 다른 요소(<a>, <Link> 등)로 렌더링할 수 있게 해줍니다.cva : class-variance-authority의 함수로, variant, size 등의 props에 따라 Tailwind 클래스 조합을 관리합니다.cn : clsx와 tailwind-merge를 기반으로 한 유틸 함수로, 조건부 클래스 처리와 중복 Tailwind 클래스 병합을 담당합니다.buttonVariants : 버튼 컴포넌트의 스타일 규칙을 정의한 객체로,variant와 size 값에 따라 적용될 Tailwind 클래스 조합을 결정합니다.
import { Button } from "@/components/ui/button"
export default function MainPage() {
return <Button>클릭</Button>
}