shadcn/ui 와 tailwind 라이브러리 함께 사용하기

kim yeseul·2023년 11월 2일
9

라이브러리

목록 보기
5/6
post-thumbnail

tailwind는 무엇인가요?

Tailwind CSS는 모든 HTML 파일, 자바스크립트 구성 요소 및 클래스 이름에 대한 다른 템플릿을 스캔하여 해당 스타일을 생성한 후 정적 CSS 파일에 쓰는 방식으로 작동한다. 런타임이 없고 빠르고 유연하며 안정적이다.

  • 출처: tailwind 공식문서 번역

tailwind를 Next.js에 적용해보자.

1. next 프로젝트 생성 시 설치해주기

npx create-next-app@latest

➡️ next 프로젝트를 생성할 때 tailwind를 추가하고 tailwind.config.jspostcss.config.js.를 추가해준다. (명령어에서 y를 눌러 생성해준다.)

2. tailwind.config.js에 사용할 경로 작성하기

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
 
    // Or if using `src` directory:
    "./src/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

➡️ tailwind.config.js 에 content 배열 안에 모든 템플릿 파일에 대한 경로와 확장자를 위와 같이 넣어준다.

3. globals.css 작성하기

@tailwind base;
@tailwind components;
@tailwind utilities;

➡️ globals.css 상단에 각 레이어에 대한 지시문을 추가해준다.

4. 적용하기

export default function Home() {
  return (
    <h1 className="text-3xl font-bold underline">
      Hello world!
    </h1>
  )
}

shadcn/ui은 무엇인가요?

Radix UI 및 Tailwind CSS를 사용하여 구축된 재사용 가능한 컴포넌트. 컴포넌트 라이브러리가 아닌 앱에 copy and paste 하여 재사용 가능한 구성요소 모음이다. npm install 을 하여 종속성 설치를 하지 않는다는 뜻이다.
...
shadcn에서 디자인된 컴포넌트를 copy and paste로 간편하게 사용 가능하다.

shadcn/ui를 Next.js에 적용해보자.

1. 먼저 next 프로젝트에 tailwind가 있다는 가정하에 사용이 가능하다.

2. Run the CLI

npx shadcn-ui@latest init

3. components.json 구성하기

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? › › app/globals.css
Do you want to use CSS variables for colors? › no / yes
Where is your tailwind.config.js located? › tailwind.config.js
Configure the import alias for components: › @/components
Configure the import alias for utils: › @/lib/utils
Are you using React Server Components? › no / yes

4. 사용할 컴포넌트 추가하기

npx shadcn-ui@latest add button

5. 컴포넌트에 사용하기

import { Button } from "@/components/ui/button"

export default function Home() {
  return (
    <div>
      <Button>Click me</Button>
    </div>
  )
}

더 많은 컴포넌트와 variant는 아래 링크에서 확인할 수 있다.
https://ui.shadcn.com/docs/components/accordion

두 조합이 같이 사용되는 이유는 무엇일까?

간단히 말하자면 호환성 때문!

shadcn/uitailwind css 시스템을 도입하고 있기 때문이고 또, 기존 npx 이후 커스텀 또한 가능한 장점이 있다.

tailwind로 shadcn/ui 커스텀을 어떻게 할 수 있나요?

실제 적용한 코드를 통해 알아보자.

먼저 shadcn 기존 Button 을 추가한 경우 아래와 같은 코드가 생성된다.

import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import * as React from 'react';

import { cn } from '@/lib/utils';

const buttonVariants = cva(
  'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
  {
    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',
        secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
        ghost: 'hover:bg-accent hover:text-accent-foreground',
        link: 'text-primary underline-offset-4 hover:underline',
      },
      size: {
        default: 'h-10 px-4 py-2',
        sm: 'h-9 rounded-md px-3',
        lg: 'h-11 rounded-md px-8',
        icon: 'h-10 w-10',
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'default',
    },
  },
);

export interface IButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, IButtonProps>(
  ({ 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 };

이제 variant 종류와 font-weight, font-size를 추가해 커스텀 해 볼 것이다.

variants: {
  variant: {
    // ...
  	// 진한 네이비 버튼
    deepnavy:
    'bg-primaryBlue-700 text-[14px] text-white rounded-[0px] hover:bg-primaryBlue-700/80 transition-all duration-300',
    // 연한 블루 버튼
    lightblue:
      'bg-primaryBlue-100 text-[14px] font-bold text-primaryBlue-default rounded-full hover:bg-primaryBlue-100/50 transition-all duration-300',
  },
  // ... 생략
  font: {
    default: 'text-sm', // 기본 14px
    xs: 'text-xs', // 12px
    base: 'text-base', // 16px
    lg: 'text-lg', // 18px
    xl: 'text-xl', // 20px
  },
  weight: {
    default: 'font-normal',
    medium: 'font-medium',
    bold: 'font-bold',
  },
},
defaultVariants: {
  variant: 'default',
  size: 'default',
  font: 'default', // defaultVariants에 추가
  weight: 'default', // defaultVariants에 추가
}
  
// 추가 속성을 props에 전달
const Button = React.forwardRef<HTMLButtonElement, IButtonProps>(
  ({ className, variant, size, font, weight asChild = false, ...props }, ref) => {
    const Comp = asChild ? Slot : 'button';
    return (
      <Comp className={cn(buttonVariants({ variant, size,  font, weight, className }))} ref={ref} {...props} />
    );
  },
);

➡️ variants 객체 안에 추가된 속성인 font, weight의 css를 작성해주고 기존 variant는 스타일을 추가해준다.
➡️ Button props에 추가된 속성인 font, weight를 추가해주면 끝!

사용 예시

<Button variant='lightblue' font='xs'>
  중복 확인
</Button>

default 속성을 사용할 경우 속성을 전달할 필요가 없다!

그럼 여기서 cn은 뭘까?

// src/lib/utils.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

export const cn = (...inputs: ClassValue[]) => {
  return twMerge(clsx(inputs));
};

➡️components.json 구성 시에 Configure the import alias for utils: › @/lib/utils 경로를 설정해주었을 때 해당 경로에 생성되는 utils.ts 파일인데 해당 파일은 tailwind를 merge할 때 발생할 수 있는 클래스 충돌 문제를 해결해주는 역할을 한다.

profile
출발선 앞의 준비된 마음가짐, 떨림, 설렘을 가진 신입개발자

0개의 댓글