여러분, 안녕하세요! 코딩하다 막히면 언제든 찾아와 해답을 얻어갈 수 있는 여러분의 든든한 프론트엔드 사수, codeBricks입니다.
지난 2편에서는 이름 짓기의 고통을 없애고 개발 속도를 비약적으로 끌어올려 주는 마법의 도구, '테일윈드(Tailwind CSS)'에 대해 알아보았습니다. 자, 이제 우리는 테일윈드라는 강력한 무기를 얻었습니다. 그런데 막상 프로젝트를 시작하려고 하니 또 다른 막막함이 밀려옵니다.
"테일윈드가 좋은 건 알겠는데... 그럼 버튼 하나, 모달 창 하나, 드롭다운 메뉴 하나를 언제 다 처음부터 끝까지 테일윈드 클래스명을 쳐서 만들고 앉아있지?"
맞습니다. 아무리 레고 조립이 재밌다 한들, 매번 바퀴부터 새로 만들 수는 없는 노릇이죠. 과거에는 이런 귀찮음을 해결하기 위해 npm install @mui/material이나 bootstrap 같은 거대한 UI 라이브러리를 통째로 설치해서 썼습니다. 하지만 2026년 현재, 프론트엔드 생태계의 패러다임은 완전히 뒤집혔습니다. 전 세계 개발자들의 열광적인 지지를 받으며 '생태계의 절대 존엄'으로 군림하고 있는 새로운 방식, 바로 'Shadcn UI (섀드씨엔 UI)'의 등장 때문입니다.
오늘은 기존 라이브러리의 한계를 완벽하게 부수고, 개발자에게 디자인의 자유를 되찾아준 Shadcn UI의 철학과 뼛속까지 파헤치는 코드 분석을 진행해 보겠습니다.
1. "내 코드를 지배하지 마!" – 기존 UI 라이브러리(npm install)의 한계
Shadcn UI가 왜 위대한지 알기 위해서는, 우리가 그동안 써왔던 전통적인 UI 라이브러리들의 치명적인 단점을 먼저 이해해야 합니다.
우리가 Material UI나 Ant Design 같은 프레임워크를 npm install 명령어로 설치하면, 그 방대한 코드들은 우리 눈에 보이지 않는 node_modules라는 깊고 어두운 블랙박스 폴더 안으로 숨어버립니다. 화면에 <Button>을 불러와서 쓰면 당장은 예쁩니다. 하지만 기획자나 디자이너가 이렇게 말하는 순간 비극이 시작됩니다.
"개발자님, 이 버튼 클릭했을 때 나오는 파동(Ripple) 효과 좀 없애주시고요, 모서리 둥글기(border-radius)를 2px만 더 깎아주세요."
우리는 당황합니다. 버튼의 원본 코드는 node_modules 안에 잠겨 있기 때문에 직접 수정할 수가 없거든요. 결국 라이브러리 공식 문서를 뒤져서 복잡한 Theme Provider를 세팅하거나, 억지로 !important 속성을 떡칠해가며 남이 짜놓은 스타일을 '덮어쓰기(Override)' 하기 위한 피 터지는 싸움을 벌여야 합니다. 결국 주객이 전도되어, 내가 내 프로젝트를 통제하는 것이 아니라 외부 라이브러리가 내 프로젝트의 디자인 주도권을 쥐고 흔드는 끔찍한 상황이 발생합니다.

2. "설치하지 마세요. 당신의 소유로 만드세요." – Shadcn UI의 철학
이러한 고통 속에서 혜성처럼 등장한 Shadcn UI는 공식 문서 첫 줄에 이렇게 선언합니다. "이것은 컴포넌트 라이브러리가 아닙니다. npm을 통해 설치할 수 있는 패키지가 아닙니다."
그렇다면 어떻게 쓰라는 걸까요? Shadcn UI의 핵심 철학은 바로 '복사하고 붙여넣기(Copy and Paste)'입니다. 남이 만든 코드를 내 프로젝트의 node_modules에 숨기는 것이 아니라, 내 프로젝트의 components 폴더 안에 물리적인 파일(예: button.tsx)로 대놓고 복사해 와서, 내 입맛대로 직접 뜯어고쳐서 쓰라는 것입니다.
이 작은 발상의 전환이 가져온 결과는 어마어마했습니다. 더 이상 라이브러리의 규칙에 얽매일 필요가 사라졌습니다. 디자이너가 버튼 색상을 조금 바꿔달라고 하면? 그냥 내 폴더에 있는 button.tsx 파일을 열어서 테일윈드 클래스명 하나만 bg-blue-500에서 bg-blue-600으로 쓱 바꿔주면 끝납니다. 디자인의 완벽한 통제권(주도권)이 드디어 100% 개발자의 손으로 돌아온 것입니다.
3. CLI 명령어로 알아보는 Shadcn UI의 기막힌 세팅 로직
물론 공식 홈페이지에서 코드를 마우스로 드래그해서 일일이 복사할 필요는 없습니다. 아주 우아한 CLI(Command Line Interface) 명령어를 제공하니까요. 터미널 창을 열고 초기 세팅을 해보겠습니다.
npx shadcn-ui@latest init
이 명령어를 치면 마법 같은 일이 벌어집니다. 내 프로젝트 환경에 맞춰 테일윈드 설정 파일(tailwind.config.js)이 Shadcn의 예쁜 기본 색상표로 자동 세팅됩니다. 그리고 지난 2편에서 제가 '무조건 알아야 할 성배'라고 강조했던 테일윈드 클래스 충돌 방지 함수, cn() 로직이 lib/utils.ts 파일 안에 자동으로 착! 하고 만들어집니다.
이제 본격적으로 버튼 컴포넌트를 내 프로젝트로 가져와 볼까요?
npx shadcn-ui@latest add button
엔터를 누르는 순간, 내 프로젝트의 components/ui/ 폴더 경로에 button.tsx라는 파일이 실제로 생성됩니다. 이 파일을 열어보면 어떻게 생겼을까요?

[자동 생성된 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"
// 1. cva를 이용한 디자인 변수 선언
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",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
// 2. 컴포넌트 실제 구현부
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
이 코드를 한글로 아주 쉽게 번역해 드릴게요! 우선 가장 눈에 띄는 것은 cva (class-variance-authority)라는 도구입니다. 이 녀석은 테일윈드 클래스들을 '조건별로' 예쁘게 묶어주는 역할을 합니다. 위쪽의 기본 뼈대(inline-flex items-center...)는 모든 버튼이 공통으로 가지는 CSS입니다. 그리고 아래 variants 객체를 보시면, variant가 'default(기본)'일 때, 'destructive(빨간색 경고)'일 때, 'outline(테두리만 있는)'일 때 각각 어떤 테일윈드 색상 클래스를 입힐지 아주 직관적으로 정의되어 있습니다. 크기(size)도 마찬가지로 sm, md, lg에 따라 패딩(px, py) 값을 다르게 설정해 두었죠.
그리고 하단의 컴포넌트 렌더링 부분을 보세요! 2편에서 배운 cn() 함수가 여기서 완벽하게 사용됩니다. 사용자가 추가로 입력한 className과, cva가 계산해 낸 기본 테일윈드 클래스들을 cn() 함수에 통째로 집어넣어, 혹시라도 발생할지 모르는 충돌을 말끔하게 해결한 뒤 브라우저에 최종 버튼을 그려내는 아주 아름다운 로직입니다.
마무리하며: 기능은 Radix, 껍데기는 Tailwind
사실 컴포넌트를 직접 소유한다는 것에는 큰 책임이 따릅니다. 모달 창(Dialog)을 하나 만들 때, 시각 장애인을 위한 스크린 리더 기능(ARIA 속성)이나, 모달이 켜졌을 때 뒷배경의 스크롤을 막고 키보드 포커스를 가두는 아주 복잡한 '접근성(Accessibility)' 로직을 우리가 일일이 구현해야 할까요?
천만의 말씀입니다. Shadcn UI의 진짜 위대함은, 화면에 보이는 껍데기(디자인)는 '테일윈드 CSS'로 우리가 쉽게 뜯어고칠 수 있게 노출해 두고, 키보드 조작 같은 보이지 않는 복잡한 내부 로직(기능)은 'Radix UI'라는 완벽한 Headless UI 라이브러리를 가져와서 조립해 두었다는 점입니다. 우리는 머리 아픈 로직 구현은 세계 최고의 천재들에게 맡겨두고, 그저 껍데기만 테일윈드로 맛있게 요리해서 우리만의 포트폴리오를 찍어내기만 하면 되는 엄청난 시대에 살고 있습니다.
이제 예쁘고 단단한 기본 컴포넌트들을 내 맘대로 찍어내는 법을 배웠습니다. 다음 제4편에서는, 이런 정적인 컴포넌트들을 넘어서 애플(Apple) 웹사이트처럼 글자가 날아다니고 3D 효과가 터지는 화려한 화면을 만들어 줄 마법의 오픈소스, 'Aceternity UI'와 'Magic UI'의 세계로 여러분을 안내하겠습니다. 기대해 주셔도 좋습니다!
(지금까지 웹 개발 생태계의 혁명인 Shadcn UI의 원리와 구조에 대해 알아보았습니다. 본 포스팅은 제가 직접 프로젝트를 구축하고 리팩토링하며 겪은 실무 경험과 기술적 인사이트를 바탕으로 작성되었습니다. 모든 개발 환경은 프로젝트의 성격에 따라 최적의 도구가 다를 수 있으므로, 충분한 기술 검토 후 도입하시기를 권장합니다.)
[UI/IX 컴포넌트 오픈소스 #04] "화려한 애플(Apple) 웹사이트의 비밀", Aceternity UI & Magic UI
안녕하세요! 여러분의 밋밋한 포트폴리오에 생명력을 불어넣어 줄 프론트엔드 심폐소생술사, 웹 개발자입니다.프론트엔드 개발자로 일하다 보면 기획자나 클라이언트에게 가장 많이, 그리고
code-bricks.tistory.com