: Radix UI와 Tailwind CSS의 최신 기술을 기반으로 한 Component Collection
( Vercel의 shadcn이 만든 UI 도구 )
→ Components library가 아닌 Collection이라는 차이
→ 원하는 컴포넌트만 골라서 작성 가능
뭐가 좋을까?
init , add 명령어를 위한 script (CLI)components.json은 init 명령어에서 입력받은 프로젝트 설정(alias, 관련 파일들의 위치, rsc, tsx 사용 여부, 테마 등)을 담고있음
CLI를 사용하려면 필요한 파일
// components.json
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/app/globals.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}
프로젝트 설정에 맞춰 변경해서 사용하면 됨
프로젝트에서 관장하는 Tailwind CSS 연관 파일은 총 3개
각 파일 모두 shadcn/ui가 관리하는 템플릿이 존재하며, init 과정에서 자동으로 템플릿이 설치됨
tailwind config file
global css file
cn helper 가 들어있는 utils file
//utils.ts
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
clsx 는 객체, 배열 지원과 조건부 추가 등 className 사용을 편리하게 해주며 동적으로 className string 을 생성함tailwind-merge 는 Tailwind 유틸리티 클래스의 중복과 충돌 제거 등을 처리함원본 컴포넌트는 Typescript와 빌드 결과물 JSON 두 가지 형태로 관리됨
registry/<style>/ui/<name>.tsx 경로에 저장됨project initialize, component add, component diff 총 세 가지 기능
pnpm dlx shadcn@latest init
→ components.json 생성
pnpm dlx shadcn@latest add button
pnpm dlx shadcn@latest add 컴포넌트명 : 명령어 뒤에 원하는 컴포넌트 작성
ex. button component 추가
pnpm dlx shadcn@latest add button 실행하면 아래의 컴포넌트가 설치됨
// 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-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
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}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }
Slot 은 @radix-ui/react-slot 에서 제공하는 기능, add 명령어 실행 과정에서 자동으로 설치됨 → 컴포넌트의 자식 요소를 더 유연하게 관리할 수 있게 해줌cva 와 VariantProps 는 class-variance-authority 라이브러리에서 제공함 → CSS 클래스의 변형을 쉽게 관리할 수 있도록 해줌cn 은 클래스 이름을 결합하는데 사용하는 내부 유틸리티 함수 → 해당 파일은 tailwindcss 를 merge할 때 발생할 수 있는 클래스 충돌 문제를 해결해주는 역할을 함buttonVariants 은 버튼의 여러 스타일을 정의함Button components 은 버튼의 기능 수행함 → asChild 라는 프로퍼티가 있어, 이를 true로 설정하면 다른 컴포넌트의 자식 요소로 사용 가능 className, variant, size 와 같은 프로퍼티를 통해 버튼의 스타일 수정 가능Comp 라는 변수에 Slot 이나 button 을 할당하여 이를 통해 실제 화면에 버튼 표시 → cn 함수를 사용하여 buttonVariants 에서 정의한 스타일 적용import { Button } from "@/components/ui/button"
export default function Home() {
return (
<div>
<Button>Click me</Button>
</div>
)
}