shadcn/ui 완벽 가이드 | Radix UI·Tailwind·복사 가능한 컴포넌트·커스터마이징·실전 활용

shadcn/ui 완벽 가이드 | Radix UI·Tailwind·복사 가능한 컴포넌트·커스터마이징·실전 활용

이 글의 핵심

shadcn/ui로 아름다운 UI를 구축하는 완벽 가이드입니다. Radix UI 기반, Tailwind 스타일링, 복사 가능한 컴포넌트, 커스터마이징까지 실전 예제로 정리했습니다.

실무 경험 공유: Material-UI에서 shadcn/ui로 전환하면서, 번들 크기가 80% 감소하고 커스터마이징이 자유로워진 경험을 공유합니다.

들어가며: “UI 라이브러리가 무거워요”

실무 문제 시나리오

시나리오 1: 번들 크기가 너무 커요
Material-UI는 무겁습니다. shadcn/ui는 필요한 것만 복사합니다.

시나리오 2: 커스터마이징이 어려워요
기존 라이브러리는 제한적입니다. shadcn/ui는 코드를 직접 수정할 수 있습니다.

시나리오 3: 디자인 시스템이 필요해요
처음부터 만들기 어렵습니다. shadcn/ui는 기본 제공합니다.


1. shadcn/ui란?

핵심 특징

shadcn/ui는 복사 가능한 컴포넌트 컬렉션입니다.

주요 장점:

  • 복사 가능: npm 패키지가 아님
  • Radix UI: 접근성 완벽
  • Tailwind: 스타일링 자유
  • 커스터마이징: 코드 직접 수정
  • TypeScript: 완벽한 지원

2. 설치 및 설정

프로젝트 초기화

npx shadcn-ui@latest init

설정 질문:

  • TypeScript: Yes
  • Style: Default
  • Base color: Slate
  • CSS variables: Yes

생성된 파일

my-app/
├── components/
│   └── ui/
├── lib/
│   └── utils.ts
├── tailwind.config.js
└── components.json

3. 컴포넌트 추가

Button

npx shadcn-ui@latest add button
// app/page.tsx
import { Button } from '@/components/ui/button';

export default function Home() {
  return (
    <div>
      <Button>Click me</Button>
      <Button variant="destructive">Delete</Button>
      <Button variant="outline">Outline</Button>
      <Button variant="ghost">Ghost</Button>
      <Button size="sm">Small</Button>
      <Button size="lg">Large</Button>
    </div>
  );
}

Card

npx shadcn-ui@latest add card
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from '@/components/ui/card';

export default function UserCard() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>John Doe</CardTitle>
        <CardDescription>Software Engineer</CardDescription>
      </CardHeader>
      <CardContent>
        <p>Email: john@example.com</p>
      </CardContent>
      <CardFooter>
        <Button>View Profile</Button>
      </CardFooter>
    </Card>
  );
}

4. Form 컴포넌트

Form + Zod

npx shadcn-ui@latest add form input label
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
import { Button } from '@/components/ui/button';
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';

const formSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

export default function LoginForm() {
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      email: '',
      password: '',
    },
  });

  function onSubmit(values: z.infer<typeof formSchema>) {
    console.log(values);
  }

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input placeholder="[email protected]" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <FormField
          control={form.control}
          name="password"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Password</FormLabel>
              <FormControl>
                <Input type="password" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        <Button type="submit">Submit</Button>
      </form>
    </Form>
  );
}

5. Dialog

npx shadcn-ui@latest add dialog
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@/components/ui/dialog';

export default function UserDialog() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button>Open Dialog</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>User Details</DialogTitle>
          <DialogDescription>
            View and edit user information.
          </DialogDescription>
        </DialogHeader>
        <div>
          {/* Content */}
        </div>
      </DialogContent>
    </Dialog>
  );
}

6. Table

npx shadcn-ui@latest add table
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table';

export default function UsersTable({ users }) {
  return (
    <Table>
      <TableHeader>
        <TableRow>
          <TableHead>Name</TableHead>
          <TableHead>Email</TableHead>
          <TableHead>Role</TableHead>
        </TableRow>
      </TableHeader>
      <TableBody>
        {users.map((user) => (
          <TableRow key={user.id}>
            <TableCell>{user.name}</TableCell>
            <TableCell>{user.email}</TableCell>
            <TableCell>{user.role}</TableCell>
          </TableRow>
        ))}
      </TableBody>
    </Table>
  );
}

7. 커스터마이징

색상 변경

/* app/globals.css */
@layer base {
  :root {
    --primary: 222.2 47.4% 11.2%;
    --primary-foreground: 210 40% 98%;
  }
}

컴포넌트 수정

// components/ui/button.tsx
// 직접 수정 가능!
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) => {
    // 원하는 대로 수정
    return <button className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
  }
);

정리 및 체크리스트

핵심 요약

  • shadcn/ui: 복사 가능한 컴포넌트
  • Radix UI: 접근성 완벽
  • Tailwind: 스타일링 자유
  • 커스터마이징: 코드 직접 수정
  • TypeScript: 완벽한 지원
  • 작은 번들: 필요한 것만

구현 체크리스트

  • shadcn/ui 초기화
  • 컴포넌트 추가
  • Form 구현
  • Dialog 구현
  • Table 구현
  • 색상 커스터마이징
  • 컴포넌트 수정

같이 보면 좋은 글

  • Tailwind CSS 완벽 가이드
  • Storybook 완벽 가이드
  • Radix UI 가이드

이 글에서 다루는 키워드

shadcn/ui, Radix UI, Tailwind, UI Components, React, Design System, Frontend

자주 묻는 질문 (FAQ)

Q. Material-UI와 비교하면 어떤가요?

A. shadcn/ui가 더 가볍고 커스터마이징이 자유롭습니다. Material-UI는 더 많은 컴포넌트를 제공합니다.

Q. npm 패키지가 아닌가요?

A. 네, 코드를 직접 복사해서 사용합니다. 이게 장점입니다.

Q. 다크 모드를 지원하나요?

A. 네, next-themes와 함께 사용하면 간단히 구현할 수 있습니다.

Q. 프로덕션에서 사용해도 되나요?

A. 네, 많은 프로젝트에서 안정적으로 사용하고 있습니다.

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3