Skip to Content
ResourcesCustomization

Customization

Vortex UI를 프로젝트에 맞게 커스터마이징하는 방법


개요

Vortex는 Copy & Paste 방식으로 소스 코드를 직접 제공하여 완전한 커스터마이징 자유도를 제공합니다.

이 가이드에서는 다음 내용을 다룹니다:

  • 디자인 토큰 (Design Tokens) 커스터마이징
  • 컴포넌트 스타일 수정
  • 테마 시스템 활용
  • Dark mode 커스터마이징
  • 타이포그래피 변경
  • 색상 팔레트 수정

디자인 토큰 (Design Tokens)

CSS 변수 시스템

Vortex는 CSS 변수 기반 디자인 토큰을 사용합니다.

/* src/globals.css */ @import "tailwindcss"; @layer base { :root { /* 배경 및 전경색 */ --background: 0 0% 100%; --foreground: 222.2 84% 4.9%; /* 카드 및 팝오버 */ --card: 0 0% 100%; --card-foreground: 222.2 84% 4.9%; --popover: 0 0% 100%; --popover-foreground: 222.2 84% 4.9%; /* 프라이머리 컬러 */ --primary: 221.2 83.2% 53.3%; --primary-foreground: 210 40% 98%; /* 세컨더리 컬러 */ --secondary: 210 40% 96.1%; --secondary-foreground: 222.2 47.4% 11.2%; /* Muted 컬러 */ --muted: 210 40% 96.1%; --muted-foreground: 215.4 16.3% 46.9%; /* Accent 컬러 */ --accent: 210 40% 96.1%; --accent-foreground: 222.2 47.4% 11.2%; /* Destructive 컬러 */ --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 40% 98%; /* Border 및 Input */ --border: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%; /* Ring (Focus) */ --ring: 221.2 83.2% 53.3%; /* Radius */ --radius: 0.5rem; } .dark { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; --card: 222.2 84% 4.9%; --card-foreground: 210 40% 98%; --popover: 222.2 84% 4.9%; --popover-foreground: 210 40% 98%; --primary: 217.2 91.2% 59.8%; --primary-foreground: 222.2 47.4% 11.2%; --secondary: 217.2 32.6% 17.5%; --secondary-foreground: 210 40% 98%; --muted: 217.2 32.6% 17.5%; --muted-foreground: 215 20.2% 65.1%; --accent: 217.2 32.6% 17.5%; --accent-foreground: 210 40% 98%; --destructive: 0 62.8% 30.6%; --destructive-foreground: 210 40% 98%; --border: 217.2 32.6% 17.5%; --input: 217.2 32.6% 17.5%; --ring: 224.3 76.3% 48%; } }

색상 변경하기

Primary 색상 변경 예시:

:root { /* 기존 */ --primary: 221.2 83.2% 53.3%; /* Blue */ /* 변경 */ --primary: 142.1 76.2% 36.3%; /* Green */ --primary-foreground: 355.7 100% 97.3%; } .dark { --primary: 142.1 70.6% 45.3%; --primary-foreground: 144.9 80.4% 10%; }

결과:

모든 primary 컬러를 사용하는 Button, Link, Focus ring 등이 자동으로 Green으로 변경됩니다.

색상 선택 도구

HSL 색상 값을 쉽게 찾으려면 다음 도구를 사용하세요:


컴포넌트 커스터마이징

컴포넌트 소스 코드 수정

Vortex는 Copy & Paste 방식으로 소스 코드를 제공하므로 직접 수정할 수 있습니다.

예시: Button 컴포넌트 커스터마이징

# Button 컴포넌트 추가 npx @vortex/cli add button

컴포넌트가 src/components/ui/button.tsx에 복사됩니다.

// src/components/ui/button.tsx import { cva } from "class-variance-authority"; // variants 수정 const buttonVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", // 새로운 variant 추가 success: "bg-green-600 text-white hover:bg-green-700", warning: "bg-yellow-600 text-white hover:bg-yellow-700", }, size: { default: "h-10 px-4 py-2", sm: "h-9 px-3", lg: "h-11 px-8", // 새로운 size 추가 xl: "h-14 px-10 text-lg", }, }, } );

사용:

<Button variant="success">저장</Button> <Button variant="warning" size="xl"> 경고 </Button>

새로운 Variant 추가

Card 컴포넌트에 bordered variant 추가:

// src/components/ui/card.tsx const cardVariants = cva("rounded-lg", { variants: { variant: { default: "bg-card text-card-foreground shadow-sm", // 새로운 variant bordered: "bg-card text-card-foreground border-2 border-primary", elevated: "bg-card text-card-foreground shadow-lg", }, }, defaultVariants: { variant: "default", }, }); interface CardProps extends React.HTMLAttributes<HTMLDivElement> { variant?: "default" | "bordered" | "elevated"; } const Card = React.forwardRef<HTMLDivElement, CardProps>( ({ className, variant = "default", ...props }, ref) => ( <div ref={ref} className={cn(cardVariants({ variant }), className)} {...props} /> ) );

테마 시스템

Dark Mode 구현

1. Next.js App Router:

// app/layout.tsx import { ThemeProvider } from "next-themes"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="ko" suppressHydrationWarning> <body> <ThemeProvider attribute="class" defaultTheme="system" enableSystem> {children} </ThemeProvider> </body> </html> ); }

2. Theme Toggle 컴포넌트:

"use client"; import { Moon, Sun } from "lucide-react"; import { useTheme } from "next-themes"; import { Button } from "@/components/ui/button"; export function ThemeToggle() { const { theme, setTheme } = useTheme(); return ( <Button variant="ghost" size="sm" onClick={() => setTheme(theme === "light" ? "dark" : "light")} > <Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" /> <Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" /> <span className="sr-only">Toggle theme</span> </Button> ); }

커스텀 테마 생성

1. 테마 파일 생성:

// src/styles/themes.ts export const themes = { default: { light: { background: "0 0% 100%", foreground: "222.2 84% 4.9%", primary: "221.2 83.2% 53.3%", // ... }, dark: { background: "222.2 84% 4.9%", foreground: "210 40% 98%", primary: "217.2 91.2% 59.8%", // ... }, }, purple: { light: { background: "0 0% 100%", foreground: "222.2 84% 4.9%", primary: "262.1 83.3% 57.8%", // Purple // ... }, dark: { background: "222.2 84% 4.9%", foreground: "210 40% 98%", primary: "263.4 70% 50.4%", // Purple // ... }, }, };

2. 테마 적용 함수:

export function applyTheme( themeName: keyof typeof themes, mode: "light" | "dark" ) { const theme = themes[themeName][mode]; const root = document.documentElement; Object.entries(theme).forEach(([key, value]) => { root.style.setProperty(`--${key}`, value); }); }

3. 사용:

"use client"; import { applyTheme } from "@/styles/themes"; export function ThemeSelector() { return ( <select onChange={(e) => applyTheme(e.target.value as any, "light")}> <option value="default">Default</option> <option value="purple">Purple</option> </select> ); }

타이포그래피

폰트 변경

1. Next.js에서 Google Fonts 사용:

// app/layout.tsx import { Inter, Noto_Sans_KR } from "next/font/google"; const inter = Inter({ subsets: ["latin"], variable: "--font-sans" }); const notoSansKR = Noto_Sans_KR({ subsets: ["korean"], variable: "--font-sans-kr", }); export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="ko" className={`${inter.variable} ${notoSansKR.variable}`}> <body className="font-sans">{children}</body> </html> ); }

2. Tailwind 설정:

// tailwind.config.ts import type { Config } from "tailwindcss"; export default { theme: { extend: { fontFamily: { sans: ["var(--font-sans)", "var(--font-sans-kr)", "sans-serif"], }, }, }, } satisfies Config;

타이포그래피 스케일 커스터마이징

/* src/globals.css */ @layer base { h1 { @apply text-4xl font-bold tracking-tight lg:text-5xl; } h2 { @apply text-3xl font-semibold tracking-tight; } h3 { @apply text-2xl font-semibold tracking-tight; } p { @apply leading-7; } }

또는 Tailwind 설정에서:

// tailwind.config.ts export default { theme: { fontSize: { xs: ["0.75rem", { lineHeight: "1rem" }], sm: ["0.875rem", { lineHeight: "1.25rem" }], base: ["1rem", { lineHeight: "1.5rem" }], lg: ["1.125rem", { lineHeight: "1.75rem" }], xl: ["1.25rem", { lineHeight: "1.75rem" }], "2xl": ["1.5rem", { lineHeight: "2rem" }], "3xl": ["1.875rem", { lineHeight: "2.25rem" }], "4xl": ["2.25rem", { lineHeight: "2.5rem" }], "5xl": ["3rem", { lineHeight: "1" }], }, }, } satisfies Config;

Border Radius

Global Radius 변경

:root { /* 기존 */ --radius: 0.5rem; /* 8px */ /* 변경 */ --radius: 0.25rem; /* 4px - 더 각진 스타일 */ --radius: 1rem; /* 16px - 더 둥근 스타일 */ --radius: 9999px; /* 완전히 둥근 pill 스타일 */ }

컴포넌트별 Radius 변경

// src/components/ui/button.tsx const buttonVariants = cva( // 기존: rounded-md (--radius 사용) // 변경: 직접 Tailwind 클래스 사용 "inline-flex items-center justify-center rounded-full ...", { // ... } );

애니메이션 & 트랜지션

Transition Duration 변경

// tailwind.config.ts export default { theme: { extend: { transitionDuration: { DEFAULT: "200ms", // 기본값 slow: "300ms", slower: "500ms", }, }, }, } satisfies Config;

커스텀 애니메이션 추가

// tailwind.config.ts export default { theme: { extend: { keyframes: { "fade-in": { "0%": { opacity: "0" }, "100%": { opacity: "1" }, }, "slide-in": { "0%": { transform: "translateY(-10px)", opacity: "0" }, "100%": { transform: "translateY(0)", opacity: "1" }, }, }, animation: { "fade-in": "fade-in 0.2s ease-out", "slide-in": "slide-in 0.3s ease-out", }, }, }, } satisfies Config;

사용:

<div className="animate-fade-in">Fade in animation</div> <div className="animate-slide-in">Slide in animation</div>

반응형 Breakpoints

Breakpoints 커스터마이징

// tailwind.config.ts export default { theme: { screens: { xs: "475px", // 추가 sm: "640px", md: "768px", lg: "1024px", xl: "1280px", "2xl": "1536px", "3xl": "1920px", // 추가 }, }, } satisfies Config;

사용:

<div className="text-sm md:text-base lg:text-lg xl:text-xl 3xl:text-2xl"> Responsive text </div>

Spacing 시스템

Custom Spacing 추가

// tailwind.config.ts export default { theme: { extend: { spacing: { 18: "4.5rem", // 72px 22: "5.5rem", // 88px 26: "6.5rem", // 104px 128: "32rem", // 512px 144: "36rem", // 576px }, }, }, } satisfies Config;

사용:

<div className="p-18 m-22">Custom spacing</div>

실전 예제

예제 1: 브랜드 컬러 적용

요구사항: 회사 브랜드 컬러(#007bff)를 primary로 사용

:root { /* HSL 변환: #007bff = 211, 100%, 50% */ --primary: 211 100% 50%; --primary-foreground: 0 0% 100%; } .dark { --primary: 211 100% 60%; /* 약간 밝게 */ --primary-foreground: 0 0% 100%; }

결과: 모든 Button, Link, Focus ring이 브랜드 컬러로 변경됩니다.

예제 2: 둥근 스타일 테마

요구사항: 모든 컴포넌트를 pill 형태로 변경

:root { --radius: 9999px; }

추가 조정:

// src/components/ui/card.tsx const Card = React.forwardRef<HTMLDivElement, CardProps>( ({ className, ...props }, ref) => ( <div ref={ref} className={cn("rounded-3xl ...", className)} {...props} /> ) );

예제 3: 다크 모드 우선 테마

요구사항: 기본이 Dark mode, 옵션으로 Light mode 제공

// app/layout.tsx <ThemeProvider attribute="class" defaultTheme="dark" enableSystem={false}> {children} </ThemeProvider>
/* 기본을 dark로 */ :root { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; /* ... */ } /* light를 옵션으로 */ .light { --background: 0 0% 100%; --foreground: 222.2 84% 4.9%; /* ... */ }

고급 커스터마이징

CSS-in-JS 통합 (Emotion/Styled Components)

Vortex는 Tailwind CSS를 사용하지만 CSS-in-JS와 함께 사용 가능합니다.

import styled from "@emotion/styled"; import { Button } from "@/components/ui/button"; const StyledButton = styled(Button)` background: linear-gradient(to right, #007bff, #00d4ff); color: white; border: none; &:hover { background: linear-gradient(to right, #0056b3, #00a8cc); } `;

PostCSS Plugins 추가

// postcss.config.js export default { plugins: { "@tailwindcss/postcss": {}, autoprefixer: {}, // 추가 플러그인 "postcss-preset-env": { features: { "nesting-rules": true }, }, }, };

문제 해결

스타일이 적용되지 않음

원인 1: globals.css import 누락

// app/layout.tsx import "@/globals.css"; // 필수!

원인 2: Tailwind content 설정 누락

// tailwind.config.ts export default { content: [ "./src/**/*.{js,ts,jsx,tsx,mdx}", // 모든 컴포넌트 경로 포함 ], } satisfies Config;

원인 3: CSS 변수 우선순위

/* 잘못된 예 */ :root { --primary: blue; /* 문자열 X */ } /* 올바른 예 */ :root { --primary: 211 100% 50%; /* HSL 값 */ }

Dark mode 전환 시 깜빡임

해결책: suppressHydrationWarning 추가

<html lang="ko" suppressHydrationWarning> <body>{children}</body> </html>

다음 단계

Last updated on