[shadcn-vue] 소개 및 설치
이번 포스트에서는 vite에서 사용할 수 있는 UI 컴포넌트 라이브러리 중 shadcn이라는 것을 소개하고 프로젝트에 설치해보자.
shadcn-vue
shadcn-vue?
shadcn-vue은 화면을 만들 때 사용하는 라이브러리이다. (shadcn은 react 용이고 shadecn-vue가 vue 용이다.)그런데 막상 홈피를 방문해보면 살짝은 갸우뚱하게 하는 메시지가 반긴다.
앱에 복사해서 붙여넣을 수 있는 아름답게 디자인된 컴포넌트인데 만들어 쓰라니 약간 갸우뚱 하다. docs에서 소개를 보니 이해가 되긴 한다. Tailwind CSS 기반으로 만들어졌다는 것도 기억 할만하다.
This is NOT a component library. It's a collection of re-usable components that you can copy and paste into your apps.
What do you mean by not a component library?
I mean you do not install it as a dependency. It is not available or distributed via npm. Pick the components you need. Copy and paste the code into your project and customize to your needs. The code is yours. Use this as a reference to build your own component libraries.
일반적으로 Vuetify나 AntDesign등은 라이브러리를 설치해서 사용한다. 그래서 런타임에도 해당 라이브러리가 필요하다. 하지만 shadcn은 런타임에 참조되는 그런 라이브러리가 아니라 코드를 설치하기 위한 라이브러리이고 이 코드는 오픈소스여서 추가로 필요한 내용들을 직접 작성해서 사용할 수 있다. 물론 기본도 충분히 아름답지만 그만큼 커스터마이징이 용이하다는 이야기이다.
설치
vite 기반의 설치
https://ui.shadcn.com/docs/installation/vite를 참조하여 project를 생성하고 shadcn을 설치해보자.
먼저 다음의 과정을 거쳐 vite 기반의 프로젝트를 생성한다.
❯ npm create vue@latest
✔ Project name: … vue_shadcn_test
cd vue_shadcn_test
npm install
npm run dev
tailwind와 관련된 설정 추가
다음으로 tailwindcss 등 필요한 라이브러리를 설치해준다.
npm install -D tailwindcss autoprefixer
vite.config.js 수정
공홈에 나와있는 내용은 react 기반인데 vue 기반으로는 다음과 같이 수정해준다.
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
import autoprefixer from 'autoprefixer' // 1. autoprefixer 추가
import tailwind from 'tailwindcss' // 2. tailwind 추가
// https://vite.dev/config/
export default defineConfig({
css: { // 3. css/postcss/plugins 추가
postcss: {
plugins: [tailwind(), autoprefixer()],
},
},
plugins: [vue(), vueDevTools()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
})
shadcn-vue 초기화
shadcn-vue 사용을 위한 초기화 작업을 진행한다. 작업은 npx shadcn-vue@latest init으로 진행하며 대화형으로 설정된다.
❯ npx shadcn-vue@latest init
✔ Would you like to use TypeScript? (recommended)? … no
✔ Which framework are you using? › Vite
✔ Which style would you like to use? › New York // 스타일: 취향껏
✔ Which color would you like to use as base color? › Zinc // 기본색: 취향껏
✔ Where is your jsconfig.json file? … ./jsconfig.json
✔ Where is your global CSS file? (this file will be overwritten) … src/assets/main.css
✔ Would you like to use CSS variables for colors? … no / yes // 색상 변수: 필요시
✔ Are you using a custom tailwind prefix eg. tw-? (Leave blank if not) …
✔ Where is your tailwind.config located? (this file will be overwritten) … tailwind.config.js
✔ Configure the import alias for components: … @/components
✔ Configure the import alias for utils: … @/lib/utils
✔ Write configuration to components.json. Proceed? … yes
대부분 기본 값이나 취향에 맞춰서 선택하면 되는데 global CSS file은 main.js에서 import 하는 src/assets/main.css 를 지정하는 것이 좋다.
위 설정은 components.json 파일에 저장되며 필요시 수정이 가능하다.
{
"$schema": "https://shadcn-vue.com/schema.json",
"style": "new-york",
"typescript": false,
"tsConfigPath": "./jsconfig.json",
"tailwind": {
"config": "tailwind.config.js",
"css": "src/assets/main.css",
"baseColor": "zinc",
"cssVariables": true,
"prefix": ""
},
"framework": "vite",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}
사용해보기
Button 설치
가장 간단한 것 중 하나인 Button을 사용해보자. 페이지로 이동해서 적절한 Button을 골랐다면 [Installation] 부분에 사용법이 잘 나와있다. 일단 다음 명령으로 button 에 대한 컴포넌트를 추가한다.
npx shadcn-vue@latest add button
이 부분이 다른 컴포넌트 라이브러리와의 차이점이다. 다른 것들은 설치하면 모든 컴포넌트들이 몽땅 설치되지만 sadecn의 경우에는 필요한 컴포넌트가 있다면 개별적으로 설치한다. 이때 설치되는 것은 "소스코드"이다. 즉 마음대로 편집한다.
일단 코드는 components.json에 설정되어있는 aliases/components/에 추가된다.
기본 사용법
위와 같이 Button 컴포넌트가 생겼기 때문에 사용법은 일반 컴포넌트 사용법과 동일하다. 다음은 App.vue의 예이다.
<script setup>
import {Button} from '@/components/ui/button'
</script>
<template>
<Button>Button</Button>
</template>
<style lang=""></style>
너무나 손쉽게 잘 디자인된 버튼을 얻을 수 있다. 하지만 이게 끝이 아니다. 우리에게는 Button 컴포넌트의 소스코드가 있고 이를 이용해서 우리만의 컴포넌를 만들 수 있다.
Button 컴포넌트의 활용
버튼의 코드를 살펴보고 어디를 수정할 수 있는지 고민해보자.
<script setup>
import { cn } from '@/lib/utils';
import { Primitive } from 'radix-vue';
import { buttonVariants } from '.';
// 필요하면 자식 컴포넌트에게 전달 가능
const props = defineProps({
variant: { type: null, required: false },
size: { type: null, required: false },
class: { type: null, required: false },
asChild: { type: Boolean, required: false },
as: { type: null, required: false, default: 'button' },
});
</script>
<template>
<Primitive
:as="as"
:as-child="asChild"
:class="cn(buttonVariants({ variant, size }), props.class)"
>
<!--slot을 통해서 상위 컴포넌트에서 삽입 가능-->
<slot />
</Primitive>
</template>
일단 한눈에 바로 보이는 것은 defineProps와 slot이다. 상위 컴포넌트에서 Button을 사용하면서 선언된 prop들을 내려보낼 수도 있고 slot을 통해서 필요한 ui 를 제공하는 것도 가능하다.
일단 가볍게 slot을 이용해서 ui를 공급해보자. slot을 통해서 상위 컴포넌트는 Button에서 보여줄 내욜을 손쉽게 전달할 수 있다.
<script setup>
import { Button } from '@/components/ui/button'
const clicked = () => {
alert('button clicked')
}
</script>
<template>
<Button>
<span @click="clicked">버튼</span>
</Button>
</template>
<style lang=""></style>
props 활용
먼저 props에 대해서 알아보자. varint, size, class, asChild, as 등이 내려보내질 수 있는데 이들은 무엇일까? 관련 정보는 동일한 경로에 있는 index.js 파일에 나와있다.
import { cva } from 'class-variance-authority';
export { default as Button } from './Button.vue';
export const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
variant: {
default:
'bg-primary text-primary-foreground shadow hover:bg-primary/90',
destructive:
'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
outline:
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
secondary:
'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-9 px-4 py-2',
xs: 'h-7 rounded px-2',
sm: 'h-8 rounded-md px-3 text-xs',
lg: 'h-10 rounded-md px-8',
icon: 'h-9 w-9',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
);
물론 필요하다면 추가/수정도 가능하다. 스타일을 정의하는 방식은 당연 tailwindcss이다.
<Button variant="destructive" size="xs">
<span @click="clicked">버튼</span>
</Button>
위와 같은 방식으로 필요한 컴포넌트를 가져와서 적절히 props와 slot을 이용하면 어렵지 않게 멋진 화면을 구성할 수 있을 것 같다. 마음으로는.. ㅎ