0ju-log
💻 Frontend

[React] shadcn/ui

💡 시작하기

shadcn/ui ? Vercel의 shadcn이 만든 UI 도구로, Radix UI와 Tailwind CSS를 기반으로 하는 컴포넌트 라이브러리

:radix-ui-icon: Radix UI란?

Radix UI ? 고품질, 접근 가능한 디자인 시스템과 웹앱을 구축하는 데 사용하는 오픈소스 UI 컴포넌트 라이브러리

  • 기존의 전통적인 라이브러리들은 번들된 소스 코드를 패키지 매니저를 통해 프로젝트 의존성에 추가해 사용 BUT, shadcn/ui는 번들되지 않은 컴포넌트 소스 코드를 프로젝트에 의존성으로 추가하지 않아도 됨 즉, 복붙해서 컴포넌트 코드를 온전히 사용할 수 있음!!
  • 별도의 설치 없이 npx shadcn-ui <command>를 입력하거나, 직접 사이트에서 가져가서 사용

⚙️ 특징

직접 소스 코드 관리 가능

  • 기존의 전통적인 라이브러리들은 번들된 소스 코드를 패키지 매니저를 통해 프로젝트 의존성에 추가해 사용 BUT, shadcn/ui는 번들되지 않은 컴포넌트 소스 코드를 프로젝트에 의존성으로 추가하지 않아도 됨 즉, 복붙해서 컴포넌트 코드를 온전히 사용할 수 있음!!
  • 별도의 설치 없이 npx shadcn-ui <command>를 입력하거나, 직접 사이트에서 가져가서 사용

⇒ 라이브러리 업데이트로 인한 스타일 변경 문제를 최소화할 수 있음

⇒ 프로젝트에 맞게 자유롭게 커스터마이징 가능!

Radix UI & Tailwind CSS 기반

shadcn/ui는 Radix UI의 접근성 높은 컴포넌트와 Tailwind CSS의 유틸리티 클래스 스타일링을 결합하여 개발 효율성을 극대화함!

  • Radix UI: 모달, 드롭다운, 토글 등 기본 UI 컴포넌트의 접근성을 보장
  • Tailwind CSS: 유틸리티 클래스 기반 스타일링으로 빠르고 일관된 디자인 적용 가능

트리 쉐이킹(Tree-shaking) 지원

불필요한 코드가 포함되지 않고, 사용하는 컴포넌트만 프로젝트에 포함되도록 설계되어 번들 크기를 최소화할 수 있음

완전한 커스터마이징 가능

shadcn/ui의 모든 컴포넌트는 직접 코드로 가져와서 사용하기 때문에,

  • 디자인 시스템과 완전히 일치하도록 수정 가능
  • 프로젝트 스타일 가이드에 맞춰 확장 가능
  • 사용하지 않는 불필요한 기능 제거 가능

🖥️ 사용 방법

1️⃣ 초기화

npx shadcn-ui@latest init 명령어를 실행하면 아래 질문 중에 프로젝트 환경에 맞는 적절한 것들이 프롬프트로 표시된다. 각 질문에 맞는 값을 입력하면 설정이 완료된다.

- Would you like to use TypeScript (recommended)? › no / yes
- Which style would you like to use? › Default
- Which color would you like to use as base color? › Slate
- Where is your global CSS file? › src/index.css
- Do you want to use CSS variables for colors? › no / yes
- Are you using a custom tailwind prefix eg. tw-?
- Where is your tailwind.config.js located? › tailwind.config.js Leave blank if not) ›
- Configure the import alias for components: › @/components
- Configure the import alias for utils: › @/lib/utils
- Are you using React Server Components? › no / yes

2️⃣ npx 명령어로 컴포넌트 추가

npx shadcn-ui add button

위 명령어를 실행하면 components/ui/button.tsx와 같이 프로젝트 내에서 직접 사용할 수 있는 코드가 추가된다.

📜 예제 코드

components/ui/button.tsx
// 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 ...",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        ...
      },
      size: {
        default: "h-10 px-4 py-2",
        ...
      },
    },
    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 }

cva, buttonVariants

const buttonVariants = cva(
  "inline-flex items-center ...",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        ...
      },
      size: {
        default: "h-10 px-4 py-2",
        ...
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)
  • cva : Tailwind CSS 클래스를 동적으로 조합할 수 있도록 도와주는 라이브러리인 CVA의 핵심 함수
    • cva를 활용하면 다양한 스타일 변형 선언하고, 이를 props로 제어할 수 있음
  • buttonVariants : 버튼의 기본 스타일 변형 정의
    • variant: 버튼의 색상 및 스타일 지정 (default, outline, destructive, …)
    • size: 버튼 크기 지정 (default, sm, lg, …)

export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>,
  VariantProps<typeof buttonVariants> {
	  asChild?: boolean
	}
  • cva에서 정의한 variant 및 size 값을 props로 받을 수 있도록 지정
  • asChild 속성 추가
    • asChild={true} 설정 → 다른 태그로 변경 가능 (Radix UI의 Slot)

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}
      />
    )
  }
)
  • 태그 변경 지원 (asChild 사용)
    • asChild={true}라면 Slot을 사용하여 다른 태그로 변경 가능
  • 동적 클래스 적용
    • cn(buttonVariants({ variant, size, className }))를 통해 cva에서 정의한 스타일을 적용
    • 추가적인 클래스 전달하여 스타일을 커스터마이징할 수도 있음!

사용하기

// Home.tsx

import { Button } from "@/components/ui/button";
  
export default function Home() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <Button onClick={() => setCount(count + 1)}>Easy</Button>
    </div>
  );
}

🔗 참고

https://ui.shadcn.com/

https://pyjun01.github.io/v/shadcn-ui/

https://risingstars.js.org/2024/en#section-all