Shadcn UI: Tailwind CSS 기반의 Radix UI 컴포넌트 컬렉션

재사용 가능한 컴포넌트 컬렉션

Shadcn UI란?

Shadcn UI는 Radix UI 및 Tailwind CSS를 기반으로 한 재사용 가능한 컴포넌트 컬렉션입니다. 이 라이브러리는 앱 개발 시 직접적인 복사 및 붙여넣기를 통해 쉽게 사용할 수 있는 구조로 설계되었습니다. 개발자들은 별도의 종속성 설치 없이 이 컴포넌트들을 자신의 프로젝트에 통합할 수 있으며, npm을 통한 배포나 설치가 필요하지 않습니다.

공식 문서 및 자세한 설명은 Shadcn UI 공식 웹사이트 및 문서 페이지에서 확인할 수 있으며, 이 문서들은 컴포넌트의 설치 및 활용 방법에 대한 구체적인 정보를 제공하여, 개발자가 더 효과적으로 자신의 앱에 적용할 수 있도록 돕습니다.

이런 접근 방식은 개발 과정을 간소화하고, 효율적인 코드 재사용을 가능하게 하여, 애플리케이션 개발의 속도와 품질을 동시에 향상시킬 수 있습니다.

Shadcn UI 설치 및 기본 설정

Shadcn UI는 Next.js 프로젝트에 Tailwind CSS가 설치된 상태에서 사용할 수 있는 컴포넌트 라이브러리입니다. 이 라이브러리를 통해 개발자들은 쉽고 빠르게 UI 컴포넌트를 자신의 프로젝트에 통합할 수 있습니다. 설치를 시작하려면, 다음 명령을 실행하세요:

npx shadcn-ui@latest init
or
pnpm dlx shadcn-ui@latest init

components.json 설정하기

설치 후, components.json 파일을 구성하는 여러 옵션이 제시됩니다. 여러 설정 중 기본 옵션을 선택하는 과정은 아래와 같습니다:

Would you like to use TypeScript (recommended)? 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? › 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

설정 후, 프로젝트의 폴더 구조는 다음과 같이 구성됩니다:

├── app
│   ├── layout.tsx
│   └── page.tsx
├── components
│   ├── ui
│   │   ├── alert-dialog.tsx
│   │   ├── button.tsx
│   │   ├── dropdown-menu.tsx
│   │   └── ...
├── lib
│   └── utils.ts
├── styles
│   └── globals.css
└── tailwind.config.js

utils.ts 및 CSS 설정

lib/utils.ts 파일에는 다음과 같은 유용한 함수가 포함되어 있으며, 이 함수는 클래스 이름을 합치고 최적화하는 데 사용됩니다:

import { clsx, ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...classes: ClassValue[]) {
  return twMerge(clsx(classes));
}

Tailwind CSS 설정 변경하기

styles/globals.css 파일과 tailwind.config.js 파일에서 기본 색상 값을 커스텀하거나 다음과 같이 설정할 수 있습니다:

/* styles/globals.css */
@layer base {
 :root {
   --background: 0 0% 100%;
   --foreground: 222.2 47.4% 11.2%;
   ...
 }

 .dark {
   --background: 224 71% 4%;
   --foreground: 213 31% 91%;
   ...
 }
}
// tailwind.config.js
module.exports = {
 theme: {
   extend: {
     colors: {
       background: "hsl(var(--background))",
       foreground: "hsl(var(--foreground))",
       ...
     }
   }
 }
}

shadcn-ui 컴포넌트 설치하기

shadcn-ui 컴포넌트는 아래의 명령어를 사용해서 개별 설치할 수 있습니다.

npx shadcn-ui@latest add [component]
or
pnpm dlx shadcn-ui@latest add [component]

만약 컴포넌트를 선택해서 설치하고 싶다면 아래와 같이 컴포넌트를 지정하지 않으면 됩니다.

npx shadcn-ui@latest add

위와 같이 입력하면, 아래와 같이 컴포넌트를 선택할 수 있는 프롬프트가 표시됩니다.

Which components would you like to add? › Space to select. A to toggle all.
Enter to submit.

◯  accordion
◯  alert
◯  alert-dialog
◯  aspect-ratio
◯  avatar
◯  badge
◯  button
◯  calendar
◯  card
◯  checkbox
...

Shadcn UI Dialog 컴포넌트 만들기

Shadcn UI 라이브러리를 사용하여 Dialog 컴포넌트를 추가하는 방법은 Tailwind CSS@radix-ui/react-dialog에 의존하며, 복잡한 스타일 설정 없이 쉽게 커스텀할 수 있습니다.

1. 컴포넌트 추가하기

먼저, 다음 명령을 실행하여 Dialog 컴포넌트를 프로젝트에 추가합니다:

npx shadcn-ui@latest add dialog

2. 컴포넌트 파일 구조

이 명령을 실행하면 components/ui 폴더 내에 Dialog 파일이 생성됩니다. 이 파일들은 @radix-ui/react-dialog를 사용하여 구성되며, 기본적으로 Tailwind CSS 스타일이 적용되어 있습니다.

3. Dialog 컴포넌트 코드 예시

아래는 Dialog 컴포넌트의 전체 코드 예시입니다. 이 코드는 다양한 Dialog 요소를 정의하고, 클래스 유틸리티 함수를 사용하여 스타일을 적용합니다:

import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";

const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger;
const DialogContent = React.forwardRef((props, ref) => (
  <DialogPrimitive.Content ref={ref} {...props} />
));
const DialogTitle = React.forwardRef((props, ref) => (
  <DialogPrimitive.Title ref={ref} {...props} />
));
const DialogDescription = React.forwardRef((props, ref) => (
  <DialogPrimitive.Description ref={ref} {...props} />
));

4. Dialog 사용 예제

이 컴포넌트를 사용하여 Dialog를 구현하는 예는 다음과 같습니다:

<Dialog>
  <DialogTrigger>Open</DialogTrigger>
  <DialogContent>
    <DialogTitle>Are you sure?</DialogTitle>
    <DialogDescription>
      This action cannot be undone. This will permanently delete your account.
    </DialogDescription>
  </DialogContent>
</Dialog>

Shadcn UI 커스텀 Button 컴포넌트 만들기

Shadcn UI를 사용하여 Tailwind CSS 기반의 커스텀 Button 컴포넌트를 개발하는 방법을 설명합니다. 이 과정은 쉽게 따라 할 수 있으며, 기본 Button 스타일에 다양한 변형을 추가하여 사용자의 요구에 맞게 조정할 수 있습니다.

1. 기본 Button 컴포넌트 코드

Shadcn UI에서 제공하는 기본 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',
        // 기타 버전 설정 생략
      },
      size: {
        default: 'h-10 px-4 py-2',
        // 기타 사이즈 설정 생략
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'default',
    },
  },
);

2. Button 컴포넌트의 커스텀화

이제 Button 컴포넌트를 다음과 같이 확장하여 새로운 스타일을 적용할 수 있습니다:

// 커스텀 버전과 스타일 추가
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',
    xs: 'text-xs',
    base: 'text-base',
    lg: 'text-lg',
    xl: 'text-xl',
  },
  weight: {
    default: 'font-normal',
    medium: 'font-medium',
    bold: 'font-bold',
  },
},
defaultVariants: {
  variant: 'default',
  size: 'default',
  font: 'default',
  weight: 'default',
}

// Button 컴포넌트 확장
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} />
    );
  },
);

3. 커스텀 Button 사용 예시

커스텀 Button 컴포넌트를 사용하는 예는 다음과 같습니다:

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

멀티페이지 폼 생성 및 유효성 검사 최적화 예제

멀티페이지 폼은 사용자가 여러 페이지에 걸쳐 정보를 입력하는 형태로, 복잡한 데이터 입력 과정을 단계별로 나누어 처리합니다.

Zod를 활용한 유효성 검사

Zod는 TypeScript 환경에서 유효성 검사를 실행하는 라이브러리로, 폼 데이터의 타입 안정성을 보장합니다. Zod는 사용자 정의 에러 메시지와 복잡한 조건을 설정하는 기능을 제공하여 폼 데이터 검증에 이상적입니다. 아래는 회원가입 폼을 위한 Zod 스키마 예시입니다:

const signUpFormSchema = z.object({
  email: z.string().email({ message: 'Email format is not valid.' }),
  password: z.string().regex(
    /^(?=.*[a-zA-Z])(?=.*[0-9]).{2,20}$/,
    'Please enter at least 2 characters that combine numbers and letters.'
  ),
  passwordCheck: z.string(),
  job: z.object({
    name: z.string().min(1),
    other: z.string()
  }).refine(data => !(data.name === 'other' && data.other === ''), {
    message: 'Please specify your job if "other".',
    path: ['other']
  })
}).refine(data => data.password === data.passwordCheck, {
  path: ['passwordCheck'],
  message: 'Passwords must match.'
});

React Hook Form의 통합 사용

React Hook Form은 React에서 폼의 상태 관리와 유효성 검사를 간편하게 할 수 있는 라이브러리입니다. Zod와 결합하여 사용하면, 폼과 유효성 검사 로직을 분리하여 더욱 깔끔하고 관리하기 쉬운 코드를 작성할 수 있습니다. 예를 들어:

import { useForm, FormProvider } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';

const form = useForm({
  resolver: zodResolver(signUpFormSchema),
  mode: 'all'
});

return (
  <FormProvider {...form}>
    <form onSubmit={form.handleSubmit(data => console.log(data))}>
      {/* 폼 필드와 버튼 */}
    </form>
  </FormProvider>
);

멀티페이지 폼에서의 페이지 관리

멀티페이지 폼에서는 useFormContext를 사용하여 각 페이지에서 폼의 상태를 관리할 수 있습니다. 각 페이지는 독립적으로 유효성을 검사하고 다음 페이지로 넘어갈 수 있는 로직을 포함합니다. 예시는 다음과 같습니다:

const { register, formState: { errors }, trigger } = useFormContext();

const onClickNext = async () => {
  const isValid = await trigger("email");
  if (isValid) {
    router.push("/signup/password");
  }
};

return (
  <div>
    <input {...register("email")} type="email" />
    {errors.email && <p>{errors.email.message}</p>}
    <button type="button" onClick={onClickNext}>Next</button>
  </div>
);

Shadcn/ui와의 통합

shadcn/ui는 웹 접근성을 강화하고, 공통 컴포넌트 시스템의 구축을 용이하게 하는 라이브러리입니다. React Hook Form과 함께 사용하면 폼 컴포넌트의 관리가 더욱 편리해집니다. 예를 들어, 에러 메시지 출력 및 스타일링을 설정할 수 있습니다:

<FormField
  control={control}
  name="email"
  render={({ field }) => (
    <FormItem>
      <FormLabel>Please enter email</FormLabel>
      <FormControl>
        <Input {...field} />
      </FormControl>
      <FormMessage />
    </FormItem>
  )}
/>

답글 남기기