shadcn_guide

📁 mamma1234/example-app 📅 2 days ago
2
总安装量
2
周安装量
#74617
全站排名
安装命令
npx skills add https://github.com/mamma1234/example-app --skill shadcn_guide

Agent 安装分布

opencode 2
gemini-cli 2
antigravity 2
codex 2
kimi-cli 2

Skill 文档

Shadcn/ui (Vue) 가이드 — moove 프로젝트

⚠️ 중요: Vue 프로젝트 전용

이 프로젝트는 Vue 3 기반입니다. Shadcn/ui의 Vue 버전을 사용합니다.

구분 ❌ React (사용 금지) ✅ Vue (이 프로젝트)
아이콘 lucide-react lucide-vue-next
애니메이션 framer-motion @vueuse/motion 또는 Tailwind transitions
Primitives @radix-ui/react-* radix-vue
Shadcn import @/components/ui (React) @/components/ui (Vue)

컴포넌트 설치

# Shadcn Vue 컴포넌트 추가
npx shadcn-vue@latest add button
npx shadcn-vue@latest add dialog
npx shadcn-vue@latest add table
npx shadcn-vue@latest add form
npx shadcn-vue@latest add select
npx shadcn-vue@latest add input
npx shadcn-vue@latest add badge
npx shadcn-vue@latest add card

설치된 컴포넌트는 components/ui/ 디렉토리에 위치합니다. 새 컴포넌트 사용 전, 먼저 components/ui/에 이미 있는지 확인하세요.


Import 경로

<script setup lang="ts">
// ✅ 올바른 import 경로
import { Button } from '@/components/ui/button'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
</script>

cn() 유틸리티

클래스 병합은 항상 cn()을 사용합니다. 조건부 클래스나 외부 클래스 주입 시 필수입니다.

<script setup lang="ts">
import { cn } from '@/lib/utils'

const props = defineProps<{
  variant?: 'default' | 'destructive' | 'outline' | 'ghost'
  size?: 'sm' | 'md' | 'lg'
  class?: string
}>()
</script>

<template>
  <button
    :class="cn(
      'inline-flex items-center justify-center rounded-md font-medium transition-colors',
      'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
      'disabled:opacity-50 disabled:cursor-not-allowed',
      {
        'bg-primary text-primary-foreground hover:bg-primary/90': props.variant === 'default',
        'bg-destructive text-destructive-foreground hover:bg-destructive/90': props.variant === 'destructive',
        'border border-input bg-background hover:bg-accent': props.variant === 'outline',
        'hover:bg-accent hover:text-accent-foreground': props.variant === 'ghost',
        'h-8 px-3 text-sm': props.size === 'sm',
        'h-10 px-4': props.size === 'md',
        'h-12 px-6 text-lg': props.size === 'lg',
      },
      props.class
    )"
  >
    <slot />
  </button>
</template>

아이콘 — lucide-vue-next

<script setup lang="ts">
// ✅ Vue 프로젝트에서는 lucide-vue-next 사용
import { Search, Plus, Trash2, Edit, ChevronRight, Loader2 } from 'lucide-vue-next'

// ❌ 절대 사용 금지 (React 전용)
// import { Search } from 'lucide-react'
</script>

<template>
  <div class="flex items-center gap-2">
    <Search class="h-4 w-4 text-muted-foreground" />
    <span>검색</span>
  </div>

  <!-- 로딩 스피너 -->
  <Loader2 class="h-4 w-4 animate-spin" />
</template>

애니메이션

간단한 전환 효과는 Tailwind utilities를 사용합니다. 복잡한 애니메이션이 필요한 경우 @vueuse/motion을 사용합니다.

<template>
  <!-- ✅ 간단한 전환 — Tailwind transitions -->
  <button class="transition-all duration-150 hover:scale-105 active:scale-95">
    클릭
  </button>

  <!-- ✅ 페이드 인/아웃 — Vue Transition 컴포넌트 -->
  <Transition
    enter-active-class="transition-opacity duration-200"
    enter-from-class="opacity-0"
    enter-to-class="opacity-100"
    leave-active-class="transition-opacity duration-150"
    leave-from-class="opacity-100"
    leave-to-class="opacity-0"
  >
    <div v-if="isVisible">콘텐츠</div>
  </Transition>
</template>

Shadcn 컴포넌트 커스터마이징

컴포넌트를 수정할 때는 components/ui/ 내 파일을 직접 편집합니다. 단, 기반 Primitive(radix-vue)는 보존하고 스타일과 props만 최소한으로 변경합니다.

<!-- components/ui/badge.vue 커스터마이징 예시 -->
<script setup lang="ts">
import { cn } from '@/lib/utils'

interface Props {
  variant?: 'default' | 'secondary' | 'destructive' | 'outline' | 'success' | 'warning'
  class?: string
}

const props = withDefaults(defineProps<Props>(), {
  variant: 'default',
})
</script>

<template>
  <div
    :class="cn(
      'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold',
      {
        'border-transparent bg-primary text-primary-foreground': props.variant === 'default',
        'border-transparent bg-secondary text-secondary-foreground': props.variant === 'secondary',
        'border-transparent bg-destructive text-destructive-foreground': props.variant === 'destructive',
        'border-transparent bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200': props.variant === 'success',
        'border-transparent bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200': props.variant === 'warning',
      },
      props.class
    )"
  >
    <slot />
  </div>
</template>

자주 쓰는 패턴

다이얼로그

<script setup lang="ts">
import { ref } from 'vue'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'

const isOpen = ref(false)
</script>

<template>
  <Button @click="isOpen = true">열기</Button>

  <Dialog v-model:open="isOpen">
    <DialogContent class="sm:max-w-md">
      <DialogHeader>
        <DialogTitle>제목</DialogTitle>
      </DialogHeader>
      <div>내용</div>
      <DialogFooter>
        <Button variant="outline" @click="isOpen = false">취소</Button>
        <Button @click="handleConfirm">확인</Button>
      </DialogFooter>
    </DialogContent>
  </Dialog>
</template>

데이터 테이블

<script setup lang="ts">
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
import { Badge } from '@/components/ui/badge'
import type { User } from '@/types'

defineProps<{ users: User[] }>()
</script>

<template>
  <Table>
    <TableHeader>
      <TableRow>
        <TableHead>이름</TableHead>
        <TableHead>이메일</TableHead>
        <TableHead>상태</TableHead>
      </TableRow>
    </TableHeader>
    <TableBody>
      <TableRow v-for="user in users" :key="user.id">
        <TableCell>{{ user.name }}</TableCell>
        <TableCell>{{ user.email }}</TableCell>
        <TableCell>
          <Badge :variant="user.is_active ? 'success' : 'secondary'">
            {{ user.is_active ? '활성' : '비활성' }}
          </Badge>
        </TableCell>
      </TableRow>
    </TableBody>
  </Table>
</template>

체크리스트

  • 컴포넌트 사용 전 components/ui/에 이미 있는지 확인
  • 없으면 npx shadcn-vue@latest add <컴포넌트명>으로 설치
  • 클래스 병합에 cn() 유틸리티 사용
  • 아이콘은 lucide-vue-next 사용 (lucide-react 절대 금지)
  • 애니메이션은 Tailwind transitions 또는 Vue <Transition> 사용
  • 컴포넌트 커스터마이징 시 radix-vue Primitive ë³´ì¡´
  • dark: 클래스로 다크 모드 호환성 확보