๐๏ธ Tailwind 9์ฅ: ํ ๋ง ์ปค์คํฐ๋ง์ด์ง
๐ ๊ฐ์
@theme ๋๋ ํฐ๋ธ์ ๋์์ธ ํ ํฐ โ ๋ธ๋๋ ์ปฌ๋ฌ, ํฐํธ, ์ปค์คํ ์ค์ผ์ผ์ Tailwind ์์คํ ์ ํตํฉํ๊ธฐ
๐ ๋ชฉ์ฐจ
- ๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
- ๐ค ์ ์์์ผ ํ๋๊ฐ
- ๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
- โ๏ธ @theme ๋๋ ํฐ๋ธ๋ก ํ ๋ง ์ ์
- ๐จ ์ปค์คํ ์์ ์ถ๊ฐ
- โ๏ธ ์ปค์คํ ํฐํธ ์ถ๊ฐ
- ๐ ์ปค์คํ Breakpoint ์ถ๊ฐ
- ๐ป ์ค์ : ์์๋ค ์ปค๋ฎค๋ํฐ ๋์์ธ ์์คํ ๊ตฌ์ถ
- ๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
- ๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
- ๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
- ๐ ๋ ์์๋ณด๊ธฐ
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 15๋ถ
๐ฏ ์ด ๋ฌธ์๋ฅผ ๋ค ์ฝ์ผ๋ฉด ํ ์ ์๋ ๊ฒ
-
@theme๋๋ ํฐ๋ธ๋ก ์ปค์คํ ์์, ํฐํธ, ๊ฐ๊ฒฉ์ Tailwind ์ ํตํฉํ ์ ์๋ค - ๋ธ๋๋ ์ปฌ๋ฌ๋ฅผ
bg-brand-500,text-brand-600์ฒ๋ผ ์ฌ์ฉํ ์ ์๋ค - ๋์์ธ ํ ํฐ๊ณผ Tailwind ํด๋์ค ์์คํ ์ ์ฐ๊ฒฐํ๋ ๊ตฌ์กฐ๋ฅผ ์ค๊ณํ ์ ์๋ค
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'
- ๐จ ์์ (๋์์ด๋): "์์ฒ ๋, ์ฐ๋ฆฌ ์๋น์ค ๋ธ๋๋ ์์์ด
#4F46E5(์ธ๋๊ณ ) ์ธ๋ฐ, ์ด๊ฑธ Tailwind ์ ์ถ๊ฐํ ์ ์๋์? ๋งค๋ฒbg-[#4F46E5]์ฐ๊ธฐ ๋๋ฌด ๋ถํธํด์." - ๐ฃ ์์ฒ : "์ปค์คํ ์์์ด์? ์ค์ ํ์ผ ์ด๋๊ฐ์์..."
- ๐ฆ ์ํธ (๋ฆฌ๋): "CSS ํ์ผ์
@theme๋ธ๋ก ์ถ๊ฐํ๋ฉด ๋ผ์. ๊ทธ๋ฌ๋ฉดbg-brand-500๊ฐ์ ํด๋์ค๋ก ์ธ ์ ์์ด์." - ๐จ ์์: "๊ทธ๋ผ ๋์ค์ ๋ธ๋๋ ์์ ๋ฐ๋์ด๋ ํ ๊ณณ๋ง ์์ ํ๋ฉด ๋๊ฒ ๋ค์!"
- ๐ฆ ์ํธ: "๋ฐ๋ก ๊ทธ๊ฒ ๋์์ธ ํ ํฐ์ ํต์ฌ์ด์์."
๐ค ์ ์์์ผ ํ๋๊ฐ
Tailwind ๊ธฐ๋ณธ ํ๋ ํธ๋ ๋ฒ์ฉ์ ์ด์ผ. ํ์ง๋ง ์ค์ ํ๋ก์ ํธ์๋ ๋ธ๋๋ ๊ณ ์ ์ ์์, ํฐํธ, ๊ฐ๊ฒฉ ๊ท์น์ด ์์ด.
์์๋ค ์ปค๋ฎค๋ํฐ๊ฐ ์ฑ์ฅํ๋ฉด์:
- ๋ธ๋๋ ์ปฌ๋ฌ (#4F46E5) ๋ฅผ 50๊ฐ ํ์ผ์
bg-[#4F46E5]๋ก ํ๋์ฝ๋ฉ - ๋์ค์ ๋ธ๋๋ ์ปฌ๋ฌ๊ฐ
#7C3AED๋ก ๋ณ๊ฒฝ โ 50๊ฐ ํ์ผ ์๋ ์์ - ํ๋๋ผ๋ ๋์น๋ฉด ํ๋ฉด์ด ์ผ๋ฃฉ๋๋ฃฉ
์ด๊ฑธ ๋ฐฉ์งํ๋ ๊ฒ์ด ๋ฐ๋ก ๋์์ธ ํ ํฐ + @theme ํตํฉ์ด์ผ.
๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
@theme= ์ค์ ํต์ ์
๊ตฐ๋์ ์ค์ ํต์ ์์์ "์ค๋๋ถํฐ ๊ตฐ๋ณต ์๊น์ ์นดํค ๋์ ์ฌ๋ฆฌ๋ธ์ผ" ๋ผ๊ณ ๋ช
๋ นํ๋ฉด ๋ชจ๋ ๋ถ๋๊ฐ ๋์์ ๋ฐ๋์ด. @theme ์์ --color-brand-500: #4F46E5 ๋ฅผ --color-brand-500: #7C3AED ๋ก ๋ฐ๊พธ๋ฉด, bg-brand-500 ์ ์ฐ๋ ๋ชจ๋ ๊ณณ์ด ๋์์ ๋ณ๊ฒฝ๋ผ.
๐ก ํ ์ค๋ก ๊ธฐ์ตํ๊ธฐ
@theme๋ Tailwind ๊ฐ ์ดํดํ๋ CSS ๋ณ์ ์ ์ฅ์. ์ฌ๊ธฐ์ ๋ณ์๋ฅผ ๋ฐ๊พธ๋ฉด ํด๋น ํ ํฐ์ ์ฐ๋ ํด๋์ค ์ ์ฒด๊ฐ ๋ฐ๋๋ค.
โ๏ธ @theme ๋๋ ํฐ๋ธ๋ก ํ ๋ง ์ ์
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
@theme๊ฐ CSS ๋ณ์(Custom Properties)์ ์ด๋ป๊ฒ ์ฐ๊ฒฐ๋๋์ง ์๋ฆฌ๋ฅผ ์ดํดํ ์ ์๋ค- Tailwind v4 (CSS ๊ธฐ๋ฐ) ์ v3 (JS ์ค์ ํ์ผ) ๋ฐฉ์์ ์ฐจ์ด์ ์ ํ ๊ธฐ์ค์ ์ ์ ์๋ค
- ์ปค์คํ ํ ํฐ์ด
bg-brand-500๊ฐ์ ํด๋์ค๋ก ์ด๋ป๊ฒ ๋ณํ๋๋์ง ๋จธ๋ฆฟ์์ ๊ทธ๋ฆด ์ ์๋ค
์์์ด Figma ๋์์ธ ํ์ผ์ ๋๊ธฐ๋ฉฐ ์ด๋ ๊ฒ ๋งํ์ด: "์์ฒ ๋, ์ด๋ฒ ์ฃผ ์ค์ผ๋ก ์ฐ๋ฆฌ ๋ธ๋๋ ์์ #4F46E5 ๊ฐ ๋ค์ด๊ฐ ๋ฒํผ์ด๋ ๋ฐฐ์ง ์ปดํฌ๋ํธ ์์ฑํด์ค ์ ์์ด์?"
์์ฒ ์ด๊ฐ ์ฒ์์ ํ ๊ฒ: ์ปดํฌ๋ํธ๋ง๋ค bg-[#4F46E5] ๋ฅผ ์ง์ ํ์ดํ. ๊ทธ๋ฐ๋ฐ ์ดํ ํ ์์ ๋์ด "๊ฒฝ์์ฌ ์กฐ์ฌ ๊ฒฐ๊ณผ ๋ณด๋ผ์ ๊ณ์ด๋ก ๋ฐ๊พธ์๊ณ ํ๋ค์. ์์ #7C3AED ๋ก ์
๋ฐ์ดํธํด์ฃผ์ธ์." ๋ผ๊ณ ์ฌ๋์ ๋ ๋ ธ์ด.
์์ฒ ์ด: "...34๊ฐ ํ์ผ์์ #4F46E5 ๋ฅผ ์ฐพ์์ ๋ฐ๊ฟ์ผ ํ๋ค๊ณ ์?"
๐ฆ ์ํธ: "๊ทธ๋์ ๋์์ธ ํ ํฐ์ @theme ์ ํ ๊ณณ์ ๋ชจ์์ผ ํ๋ ๊ฑฐ์์. @theme ๋ Tailwind ๊ฐ CSS ๋ณ์๋ฅผ ํด๋์ค๋ก ๋ณํํด์ฃผ๋ ์ค์ ์ฐฝ๊ณ ์์. ์ฐฝ๊ณ ์์ ๋ฐ๊พธ๋ฉด ์ ์ฒด๊ฐ ๋ฐ๋์ด์."
Tailwind v4 ๋ฐฉ์ (CSS ๊ธฐ๋ฐ โ ๊ถ์ฅ)
Tailwind v4 ๋ ์๋ฐ์คํฌ๋ฆฝํธ ์ค์ ํ์ผ ์์ด, CSS ํ์ผ ์์ @theme ๋ธ๋ก์์ ๋ชจ๋ ํ
๋ง๋ฅผ ์ ์ํด. ๋ธ๋ผ์ฐ์ ๊ฐ ์ดํดํ๋ CSS ๋ณ์๋ฅผ ์ง์ ์จ์ ๋ฐํ์์๋ ์ ๊ทผํ ์ ์๋ ๊ฒ ์ฅ์ ์ด์ผ.
/* app/globals.css */
@import "tailwindcss";
/* ๐ฆ ์ํธ: "@theme ์์ ๋ณ์๋ค์ด bg-brand-500, font-sans ๊ฐ์ ์ ํธ๋ฆฌํฐ ํด๋์ค๋ก ์๋ ๋ณํ๋ผ์. */
/* ๋ค์ด๋ฐ ๊ท์น์ --{์นดํ
๊ณ ๋ฆฌ}-{์ด๋ฆ}: ๊ฐ ํํ์์." */
@theme {
/* ์์ ํ ํฐ: --color-{์ด๋ฆ} โ bg-{์ด๋ฆ}, text-{์ด๋ฆ}, border-{์ด๋ฆ} ํด๋์ค ์์ฑ */
--color-brand-50: oklch(0.97 0.02 270); /* ๊ฐ์ฅ ๋ฐ์ ์ */
--color-brand-100: oklch(0.93 0.05 270);
--color-brand-200: oklch(0.86 0.10 270);
--color-brand-300: oklch(0.78 0.15 270);
--color-brand-400: oklch(0.68 0.20 270);
--color-brand-500: oklch(0.58 0.24 270); /* ๐จ ์์์ด ์์ฒญํ ๋ฉ์ธ ๋ธ๋๋ ์ */
--color-brand-600: oklch(0.50 0.22 270); /* hover ์ ์ฌ์ฉ */
--color-brand-700: oklch(0.42 0.20 270);
--color-brand-800: oklch(0.34 0.17 270);
--color-brand-900: oklch(0.27 0.13 270); /* ๊ฐ์ฅ ์ด๋์ด ์ */
/* ํฐํธ ํ ํฐ: --font-{์ด๋ฆ} โ font-{์ด๋ฆ} ํด๋์ค ์์ฑ */
--font-sans: 'Pretendard', 'Noto Sans KR', sans-serif;
--font-display: 'Noto Serif KR', Georgia, serif;
/* ์ปค์คํ
breakpoint: --breakpoint-{์ด๋ฆ} โ {์ด๋ฆ}: ๋ฐ์ํ prefix ์์ฑ */
--breakpoint-xs: 480px; /* xs:flex ์ฒ๋ผ ์ฌ์ฉ ๊ฐ๋ฅ */
--breakpoint-3xl: 1920px; /* ์ด๋ํ ๋ชจ๋ํฐ ๋์ */
/* ์ปค์คํ
spacing: --spacing-{์ด๋ฆ} โ pt-{์ด๋ฆ}, mt-{์ด๋ฆ} ๋ฑ ์์ฑ */
--spacing-18: 4.5rem; /* 72px โ 8์ ๋ฐฐ์๊ฐ ์๋ ์ปค์คํ
๊ฐ */
--spacing-22: 5.5rem; /* 88px */
}์ด์ @theme ์ ์ ์ํ ํ ํฐ๋ค์ด Tailwind ์ ํธ๋ฆฌํฐ ํด๋์ค๋ก ๊ทธ๋๋ก ์ฌ์ฉ ๊ฐ๋ฅํด:
<!-- bg-brand-500, text-brand-600 ์ด์ ์ฌ์ฉ ๊ฐ๋ฅ! -->
<div class="bg-brand-500 text-white hover:bg-brand-600">๋ธ๋๋ ๋ฒํผ</div>
<p class="font-display text-2xl">๋ธ๋๋ ํฐํธ ์ ๋ชฉ</p>
<div class="pt-18">์ปค์คํ
๊ฐ๊ฒฉ 72px</div>
<div class="xs:flex hidden">480px ์ด์์์๋ง flex</div>๋์ค์ ๋ธ๋๋ ์ปฌ๋ฌ๊ฐ ๋ฐ๋์ด๋? @theme ์ --color-brand-500 ํ ์ค๋ง ์์ ํ๋ฉด ํ๋ก์ ํธ ์ ์ฒด์ ๋ฐ์๋ผ.
Tailwind v3 ๋ฐฉ์ (tailwind.config.js)
Next.js ํ๋ก์ ํธ๊ฐ ์์ง v3 ๋ฅผ ์ฌ์ฉํ๋ค๋ฉด JS ์ค์ ํ์ผ์์ ํ
๋ง๋ฅผ ํ์ฅํด. extend ํค๋ฅผ ํตํด ๊ธฐ์กด ํ๋ ํธ๋ฅผ ์ ์งํ๋ฉด์ ์ปค์คํ
ํ ํฐ์ ์ถ๊ฐํ๋ ๋ฐฉ์์ด์ผ.
๐ฆ ์ํธ: "์ค์ํ ํฌ์ธํธ๊ฐ ์์ด์. theme.extend ์ ๋ฃ์ผ๋ฉด ๊ธฐ์กด Tailwind ์์์ ์ ์งํ๋ฉด์ ์ถ๊ฐ๋ผ์. ํ์ง๋ง theme ์ ๋ฐ๋ก colors ๋ฅผ ๋ฃ์ผ๋ฉด ๊ธฐ์กด ํ๋ ํธ๊ฐ ์ ๋ถ ๋ ์๊ฐ์. extend ๋ฅผ ๋นผ๋จน์ผ๋ฉด text-gray-500 ๊ฐ์ ๊ธฐ๋ณธ ํด๋์ค๋ ์์ด์ ธ์."
// tailwind.config.js
const { fontFamily } = require('tailwindcss/defaultTheme');
module.exports = {
theme: {
extend: {
// โ
extend ์์ ๋ฃ์ด์ผ ๊ธฐ์กด ํ๋ ํธ ์ ์ง!
colors: {
brand: {
50: '#eef2ff',
100: '#e0e7ff',
200: '#c7d2fe',
300: '#a5b4fc',
400: '#818cf8',
500: '#6366f1', // ๋ฉ์ธ ๋ธ๋๋ ์
600: '#4f46e5',
700: '#4338ca',
800: '#3730a3',
900: '#312e81',
950: '#1e1b4b',
},
},
fontFamily: {
// ๊ธฐ์กด sans ํฐํธ ์คํ ์์ Pretendard ์ถ๊ฐ (fallback ์ ์ง)
sans: ['Pretendard', ...fontFamily.sans],
display: ['Noto Serif KR', 'Georgia', 'serif'],
},
screens: {
xs: '480px', // ์ถ๊ฐ breakpoint
'3xl': '1920px', // ์ด๋ํ ํ๋ฉด
},
},
},
};| ํญ๋ชฉ | v4 (@theme in CSS) | v3 (tailwind.config.js) |
|---|---|---|
| ์ค์ ์์น | CSS ํ์ผ ์ | JS ์ค์ ํ์ผ |
| ๋ฐํ์ ์ ๊ทผ | โ CSS ๋ณ์๋ก JS์์๋ ์ ๊ทผ ๊ฐ๋ฅ | โ ๋น๋ ํ์์๋ง ํด๋์ค ์์ฑ |
| ์์ ํฌ๋งท | oklch, hsl, rgb ๋ชจ๋ | ์ฃผ๋ก hex, hsl |
| ๊ถ์ฅ | โ ์ ๊ท ํ๋ก์ ํธ | ๊ธฐ์กด v3 ํ๋ก์ ํธ ์ ์ง ๋ณด์ |
๐จ ์ปค์คํ ์์ ์ถ๊ฐ
๋ธ๋๋ ์ปฌ๋ฌ ์ ์ฒด ์ค์ผ์ผ ์ถ๊ฐ
/* globals.css โ @theme ๋ฐฉ์ */
@theme {
/* oklch: ์ต์ ์์ ํ์, ๋ ์ ํํ ์์ ํํ */
--color-brand-50: #f5f3ff;
--color-brand-100: #ede9fe;
--color-brand-200: #ddd6fe;
--color-brand-300: #c4b5fd;
--color-brand-400: #a78bfa;
--color-brand-500: #8b5cf6;
--color-brand-600: #7c3aed;
--color-brand-700: #6d28d9;
--color-brand-800: #5b21b6;
--color-brand-900: #4c1d95;
--color-brand-950: #2e1065;
/* ์๋งจํฑ ์์ (์ญํ ๊ธฐ๋ฐ ์ด๋ฆ) */
--color-success-500: #22c55e;
--color-warning-500: #f59e0b;
--color-danger-500: #ef4444;
--color-info-500: #3b82f6;
}// ์ฌ์ฉ ์
function AlertBanner({ type, message }: Props) {
const styles = {
success: 'bg-success-50 border-success-200 text-success-700',
warning: 'bg-warning-50 border-warning-200 text-warning-700',
danger: 'bg-danger-50 border-danger-200 text-danger-700',
info: 'bg-info-50 border-info-200 text-info-700',
};
return (
<div className={`rounded-lg border p-4 ${styles[type]}`}>
{message}
</div>
);
}โ๏ธ ์ปค์คํ ํฐํธ ์ถ๊ฐ
ํ๊ตญ์ด ํฐํธ (Pretendard) ์ ์ฉ
/* globals.css */
/* 1. ํฐํธ import (Google Fonts ๋๋ CDN) */
@import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/variable/pretendardvariable.css');
@import "tailwindcss";
@theme {
/* 2. @theme ์ ํฐํธ ๋ฑ๋ก */
--font-sans: 'Pretendard Variable', 'Pretendard', -apple-system,
BlinkMacSystemFont, system-ui, sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
}// 3. body ์ ๊ธฐ๋ณธ ํฐํธ ์ ์ฉ
// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ko">
{/* font-sans: --font-sans ์ ๋ฑ๋กํ Pretendard ๊ฐ ์ ์ฉ๋จ */}
<body className="font-sans antialiased">
{children}
</body>
</html>
);
}Next.js next/font ์ ํตํฉ (๊ถ์ฅ)
// app/layout.tsx
import { Inter, Noto_Sans_KR } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter',
});
const notoSansKr = Noto_Sans_KR({
subsets: ['latin'],
weight: ['400', '500', '700'],
variable: '--font-noto-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>
);
}/* globals.css */
@theme {
--font-sans: var(--font-noto-sans-kr), var(--font-inter), sans-serif;
}๐ ์ปค์คํ Breakpoint ์ถ๊ฐ
@theme {
/* ์์๋ค ์ปค๋ฎค๋ํฐ์ ์ปค์คํ
breakpoint */
--breakpoint-xs: 30rem; /* 480px โ ํฐ ์ค๋งํธํฐ */
--breakpoint-3xl: 120rem; /* 1920px โ 4K ๋ชจ๋ํฐ */
/* ๊ธฐ์กด breakpoint ์ฌ์ ์ (์ ์คํ!) */
/* --breakpoint-md: 48rem; */ /* ๊ธฐ๋ณธ 768px ์ ์ง */
}<!-- ์ปค์คํ
breakpoint ์ฌ์ฉ -->
<div class="grid grid-cols-1 xs:grid-cols-2 md:grid-cols-3 3xl:grid-cols-4">๐ป ์ค์ : ์์๋ค ์ปค๋ฎค๋ํฐ ๋์์ธ ์์คํ ๊ตฌ์ถ
/* app/globals.css โ ์์๋ค ์ปค๋ฎค๋ํฐ ์ ์ฒด ๋์์ธ ์์คํ
*/
@import "tailwindcss";
@theme {
/* ===== ๋ธ๋๋ ์์ ===== */
--color-brand-50: #f5f3ff;
--color-brand-100: #ede9fe;
--color-brand-200: #ddd6fe;
--color-brand-300: #c4b5fd;
--color-brand-400: #a78bfa;
--color-brand-500: #8b5cf6;
--color-brand-600: #7c3aed; /* ์ฃผ์ ๋ฒํผ, ๋งํฌ */
--color-brand-700: #6d28d9;
--color-brand-800: #5b21b6;
--color-brand-900: #4c1d95;
/* ===== ์๋งจํฑ ์์ ===== */
--color-success: #16a34a;
--color-warning: #d97706;
--color-danger: #dc2626;
--color-info: #2563eb;
/* ===== ํฐํธ ===== */
--font-sans: 'Pretendard Variable', -apple-system, sans-serif;
/* ===== ์ปค์คํ
Breakpoint ===== */
--breakpoint-xs: 30rem;
/* ===== ์ปค์คํ
Spacing ===== */
--spacing-18: 4.5rem;
/* ===== ์ปค์คํ
Border Radius ===== */
--radius-2xl: 1rem;
--radius-3xl: 1.5rem;
/* ===== ์ปค์คํ
Box Shadow ===== */
--shadow-card: 0 2px 8px -2px rgb(0 0 0 / 0.1), 0 4px 12px -4px rgb(0 0 0 / 0.1);
}// ๋์์ธ ํ ํฐ ๊ธฐ๋ฐ ๋ฒํผ ์ปดํฌ๋ํธ
type ButtonVariant = 'primary' | 'secondary' | 'danger';
const variantClass: Record<ButtonVariant, string> = {
primary: 'bg-brand-600 text-white hover:bg-brand-700 focus-visible:ring-brand-500',
secondary: 'bg-brand-50 text-brand-700 hover:bg-brand-100 focus-visible:ring-brand-300',
danger: 'bg-red-600 text-white hover:bg-red-700 focus-visible:ring-red-500',
};
function Button({ variant = 'primary', children, ...props }: Props) {
return (
<button
className={`
rounded-xl px-6 py-3 font-semibold text-sm
transition-all duration-200 ease-out
focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2
active:scale-95
disabled:cursor-not-allowed disabled:opacity-50
${variantClass[variant]}
`}
{...props}
>
{children}
</button>
);
}๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
| ๊ฐ๋ | ๋ฐฉ๋ฒ | ๊ฒฐ๊ณผ |
|---|---|---|
| ์ปค์คํ ์์ | @theme { --color-brand-500: ... } | bg-brand-500 ์ฌ์ฉ ๊ฐ๋ฅ |
| ์ปค์คํ ํฐํธ | @theme { --font-sans: ... } | font-sans ์ ์ ์ฉ |
| ์ปค์คํ Breakpoint | @theme { --breakpoint-xs: 30rem } | xs: ์ ๋์ฌ ์ฌ์ฉ ๊ฐ๋ฅ |
| ์๋งจํฑ ์์ | --color-success, --color-danger | ์ญํ ๊ธฐ๋ฐ ์ด๋ฆ |
| ๋์์ธ ํ ํฐ | @theme ํ ๊ณณ์์ ๊ด๋ฆฌ | ์ ์ญ ๋ณ๊ฒฝ ์ฉ์ด |
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. ์์์ด ๋ธ๋๋ ์์์ #8B5CF6 ์ผ๋ก ์ ํ๋ค. ์ด๋ฅผ Tailwind ์์ bg-brand-500, text-brand-600 ์ฒ๋ผ ์ฌ์ฉํ๋ ค๋ฉด ์ด๋์ ๋ฌด์์ ์ถ๊ฐํด์ผ ํ๋๊ฐ?
โ
์ ๋ต: CSS ํ์ผ์ @theme ๋ธ๋ก์ --color-brand-500: #8b5cf6; ๊ณผ ์ ์ฒด ์ค์ผ์ผ(50~950)์ ์ ์ํ๋ค.
๐ก ์์ธ ํด์ค:
@theme์์--color-{name}-{scale}:ํ์์ผ๋ก ์ ์ํ๋ฉด Tailwind ๊ฐ ์๋์ผ๋กbg-{name}-{scale},text-{name}-{scale},border-{name}-{scale}๋ฑ ๋ชจ๋ ์์ ์ ํธ๋ฆฌํฐ๋ฅผ ์์ฑํด.- ๋จ ํ๋์ ๊ฐ(
500)๋ง ์ถ๊ฐํด๋ ์ฌ์ฉ์ ๋์ง๋ง, ๋ณดํต ์ ์ฒด ์ค์ผ์ผ์ ์ ์ํด์ผ ๋ฐ์/์ด๋์ ๋ณํ์ ์์ ๋กญ๊ฒ ์ธ ์ ์์ด. - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "
@theme { --color-name-scale: value; }โ Tailwind ๊ฐ ๋๋จธ์ง ํด๋์ค๋ฅผ ์๋ ์์ฑ."
Q2. tailwind.config.js ์์ theme.colors ์ theme.extend.colors ์ ์ฐจ์ด๋?
โ
์ ๋ต: theme.colors ๋ ๊ธฐ์กด Tailwind ์์ ํ๋ ํธ ์ ์ฒด๋ฅผ ๊ต์ฒดํ๊ณ , theme.extend.colors ๋ ๊ธฐ์กด ํ๋ ํธ๋ฅผ ์ ์งํ๋ฉด์ ์ ์์์ ์ถ๊ฐํ๋ค.
๐ก ์์ธ ํด์ค:
theme.colors: { brand: {...} }๋ฅผ ์ค์ ํ๋ฉด ๊ธฐ์กด์blue,gray,red๋ฑ ๋ชจ๋ ๋ด์ฅ ์์์ด ์ฌ๋ผ์ง๊ณbrand๋ง ๋จ์.theme.extend.colors: { brand: {...} }๋ ๊ธฐ์กด ์์์ ๊ทธ๋๋ก ์ ์งํ๋ฉด์brand์์์ ์ถ๊ฐํด. ์ค๋ฌด์์๋ ๊ฑฐ์ ํญ์extend๋ฅผ ์ฌ์ฉํด์ผ ํด.- ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "๊ต์ฒด =
theme.colors, ์ถ๊ฐ =theme.extend.colors. ์ค๋ฌด๋ ํญ์extend."
Q3. ์์๋ค ์ปค๋ฎค๋ํฐ๊ฐ ์ฑ์ฅํด์ 480px (xs) breakpoint ๊ฐ ํ์ํด์ก๋ค. Tailwind v4 ์์ ์ถ๊ฐํ๋ ๋ฐฉ๋ฒ์?
โ
์ ๋ต: globals.css ์ @theme ๋ธ๋ก์ --breakpoint-xs: 30rem; ์ ์ถ๊ฐํ๋ค. (480px รท 16 = 30rem)
๐ก ์์ธ ํด์ค:
- Tailwind v4 ์์ breakpoint ๋
--breakpoint-{name}: {value}ํ์์ผ๋ก@theme์ ์ถ๊ฐํด. 30rem= 480px (1rem = 16px ๊ธฐ์ค).- ์ถ๊ฐ ํ
xs:grid-cols-2์ฒ๋ผxs:์ ๋์ฌ๋ฅผ ์ฌ์ฉํ ์ ์์ด. - Tailwind v3 ์์๋
tailwind.config.js์theme.extend.screens: { xs: '480px' }๋ก ์ถ๊ฐํด. - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "v4 =
@themeCSS, v3 =tailwind.config.js. ๋จ์๋ rem(16 ๋๋๊ธฐ)."
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์ค๋ @theme ๋ธ๋ก ํ๋๋ก ์์๋ค ์ปค๋ฎค๋ํฐ ์ ์ฒด ๋์์ธ ์์คํ
๊ธฐ์ด๋ฅผ ์ก์๋ค. ์์ ๋์ด ํผ๊ทธ๋ง์์ ๋ธ๋๋ ์์ ํ๋ ํธ ๋ฝ์์คฌ๊ณ , ๊ทธ๊ฑธ CSS ๋ณ์๋ก ๋ฑ๋กํ๋๋ bg-brand-600 ๊ฐ์ ํด๋์ค๊ฐ ์๊ฒจ๋ฌ๋ค.
์ํธ ๋์ด "๋์์ธ ํ ํฐ์ ๋์์ด๋์ ๊ฐ๋ฐ์ ์ฌ์ด์ ๊ณตํต ์ธ์ด์์. brand-600 ์ด๋ผ๊ณ ํ๋ฉด ๋ ๋ค ๊ฐ์ ์์ ๋ ์ฌ๋ ค์ผ ํด์" ๋ผ๊ณ ํ์
จ๋๋ฐ, ๊ทธ๊ฒ ์ ๋ง ๋ง๋ ๋ง ๊ฐ๋ค.
๐ก ์ค๋์ ๊ตํ: "๋์์ธ ํ ํฐ ํ๋๋ฅผ ์ ์ ์ํ๋ฉด, ๊ทธ๊ฑธ ์ฐธ์กฐํ๋ ์๋ฐฑ ์ค์ ์ฝ๋๊ฐ ํ ๋ฒ์ ๋ฐ๋๋ค. ์ด๊ฒ ์ ์ง๋ณด์์ ๋ณธ์ง์ด๋ค."
์ค๋ ์ผ ๋ง์น๊ณ ์ํธ ๋์ด๋ ํธ์์ ๋งฅ์ฃผ ํ ์ ํ๋ค. ๊ฐ๋ ์ด๋ ๊ฒ ํธํ๊ฒ ์๊ธฐํ๋ฉด์ ๋ฐฐ์ฐ๋ ๊ฒ ๊ณต์ ๋ฌธ์๋ณด๋ค ๋ ๋ง์ด ๋จ๋ ๊ฒ ๊ฐ๋ค. ์ข์ ํ ๋ง๋ฌ๋ค๋ ์๊ฐ์ด ์์ผ ๋ ๋ค. ๐บ