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>