๐ง Tailwind 10์ฅ: ์ปค์คํ ์คํ์ผ๊ณผ ๋๋ ํฐ๋ธ
๐ ๊ฐ์
@apply, @layer, @source ์ ์ฌ๋ฐ๋ฅธ ์ฌ์ฉ๋ฒ โ Tailwind ์ ์ปค์คํ CSS ์ ์กฐํ๋ก์ด ๊ณต์กด
๐ ๋ชฉ์ฐจ
- ๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
- ๐ค ์ ์์์ผ ํ๋๊ฐ
- ๐๏ธ @layer: CSS ๊ณ์ธต ๊ตฌ์กฐ ๊ด๋ฆฌ
- ๐ฏ @apply: ์ ํธ๋ฆฌํฐ๋ฅผ CSS ๋ก ์ถ์ถ
- ๐ก @source: ์์ค ํ์ผ ๋ช ์
- ๐ป ์ค์ : Tailwind ์ ์ปค์คํ CSS ์ ๊ณต์กด
- ๐จ @apply ๋จ์ฉ ๊ฒฝ๊ณ
- ๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
- ๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
- ๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
- ๐ ๋ ์์๋ณด๊ธฐ
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 12๋ถ
๐ฏ ์ด ๋ฌธ์๋ฅผ ๋ค ์ฝ์ผ๋ฉด ํ ์ ์๋ ๊ฒ
-
@layer base,@layer components,@layer utilities์ ์ญํ ๊ณผ ์ฐจ์ด๋ฅผ ์ค๋ช ํ ์ ์๋ค -
@apply์ ์ฌ๋ฐ๋ฅธ ์ฌ์ฉ ์๋๋ฆฌ์ค์ ๋จ์ฉ ๊ธ์ง ์ผ์ด์ค๋ฅผ ๊ตฌ๋ถํ ์ ์๋ค - Tailwind ๊ฐ ์์ค๋ฅผ ๊ฐ์งํ์ง ๋ชปํ ๋
@source๋ก ํด๊ฒฐํ ์ ์๋ค
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'
- ๐ฃ ์์ฒ : "์์ ๋,
react-markdown๋ถ์๋๋ ๋งํฌ๋ค์ด ๊ธ์ด ๋๋ฌด ๋ฐ๋ฐํ๊ฒ ๋์์.globals.css์ CSS ์ฐ๋ฉด ๋๋ ๊ฑด๊ฐ์?" - ๐ฆ ์ํธ (๋ฆฌ๋): "๊ทธ๋ฅ ์ฐ๋ฉด Tailwind ๋ ์ถฉ๋ ๋ ์ ์์ด์.
@layer์์ ๋ฃ์ด์ผ Tailwind ๊ฐ ์ฐ์ ์์๋ฅผ ์ฌ๋ฐ๋ฅด๊ฒ ๊ด๋ฆฌํด์ค์." - ๐ฃ ์์ฒ : "
@layer์?@apply๋์ ๋ค๋ฅธ ๊ฑด๊ฐ์?" - ๐ฆ ์ํธ: "๋ค๋ฅธ ๊ฑฐ์์.
@layer๋ CSS ์ฐ์ ์์ ๊ณ์ธต์ ์ ์ํ๊ณ ,@apply๋ Tailwind ํด๋์ค๋ฅผ CSS ์์ ์ฐ๋ ๊ฑฐ์์. ์ค๋ ๋ ๋ค ์ ํํ ๋ฐฐ์๋ด์."
๐ค ์ ์์์ผ ํ๋๊ฐ
์์๋ค ์ปค๋ฎค๋ํฐ๊ฐ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ฉด์ Tailwind ๋ง์ผ๋ก ํด๊ฒฐ์ด ์ ๋๋ ์ผ์ด์ค๋ค์ด ์๊ฒจ๋ฌ์ด:
- ์จ๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์คํ์ผ:
react-markdown,react-datepicker๋ฑ ์ธ๋ถ ์ปดํฌ๋ํธ์ HTML ์๋ ์ง์ Tailwind ํด๋์ค๋ฅผ ๋ถ์ผ ์ ์์ด - ์ ์ญ Base ์คํ์ผ: ํฐํธ, ๋ฐ์ค ์ฌ์ด์ง, ์คํฌ๋กค ๋์ ๊ฐ์ ์ ์ญ ์ค์ ์ CSS ๋ ์ด์ด๋ก ๊ด๋ฆฌํ๋ ๊ฒ ๋ ์ ํฉํด
- ๋ณต์กํ CSS ์
๋ ํฐ:
:not()์ฒด์ธ์ด๋ ์์ฑ ์ ํ์๋ Tailwind ํด๋์ค๋ง์ผ๋ก ํํํ๊ธฐ ์ด๋ ค์
์ด๋ฐ ์ํฉ์์ Tailwind ์ ์ปค์คํ CSS ๋ฅผ ์ถฉ๋ ์์ด ํจ๊ป ์ฐ๋ ๋ฐฉ๋ฒ์ ์์์ผ ํด.
๐๏ธ @layer: CSS ๊ณ์ธต ๊ตฌ์กฐ ๊ด๋ฆฌ
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
@layer base,@layer components,@layer utilities๊ฐ๊ฐ์ด ์ธ์ ์ ์ฉ๋๋์ง ์ดํดํ๋ค- CSS ์ฐ์ ์์ ์ถฉ๋ ์์ด ์ปค์คํ ์คํ์ผ์ ์์ ํ๊ฒ ์ถ๊ฐํ๋ ๋ฐฉ๋ฒ์ ์๋ค
- ์จ๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์คํ์ผ์ Tailwind ์ ๊ณต์กด์ํค๋ ํจํด์ ์ตํ๋ค
์์ฒ ์ด๊ฐ react-markdown ์ ๋ถ์ด๋ฉด์ ์ด๋ฐ ๋ฌธ์ ๊ฐ ์๊ฒผ์ด. ๋งํฌ๋ค์ด ๋ณธ๋ฌธ ์์ <h1>, <p>, <a> ํ๊ทธ๋ค์ด Tailwind Preflight ์ ์ํด ๊ธฐ๋ณธ ์คํ์ผ์ด ์ ๊ฑฐ๋์ด์ ๋ฐ๋ฐํ๊ฒ ๋ณด์ด๋ ๊ฑฐ์ผ.
๐ฃ ์์ฒ : "์, globals.css ์ .prose-custom h1 { font-size: 2rem; } ์ด๋ ๊ฒ ์ฐ๋ฉด ๋๋ ๊ฑฐ ์๋๊ฐ์?"
๐ฆ ์ํธ: "๊ทธ๋ ๊ฒ ์ฐ๋ฉด Tailwind ์ ์ ํธ๋ฆฌํฐ ํด๋์ค๋ ์ถฉ๋ํ ์ ์์ด์. CSS ์ฐ์ ์์(Specificity)๊ฐ ๋ค์์ผ์ ์ด๋ค ๊ฒ ์ด๊ธธ์ง ์์ธก์ด ์ด๋ ค์์ ธ์. @layer ๋ฅผ ์ฐ๋ฉด Tailwind ๊ฐ ์ฐ์ ์์ ๊ณ์ธต์ ์ง์ ๊ด๋ฆฌํด์ค์ ์ถฉ๋์ด ์์ด์."
Tailwind ๋ CSS ๋ฅผ ์ธ ๊ณ์ธต(layer) ์ผ๋ก ๋๋ ์ ๊ด๋ฆฌํด. ์ธต์ ์์ = ์ฐ์ ์์ ์์:
/* ๊ณ์ธต ์์: base โ components โ utilities (์์์ ์๋๋ก ์ฐ์ ์์ ๋์์ง) */
@layer base {
/* ๐๏ธ ๊ฐ์ฅ ๋ฎ์ ์ฐ์ ์์ */
/* HTML ์์ ๊ธฐ๋ณธ ์คํ์ผ, ์ ์ญ ํฐํธ, CSS reset ๋ฑ */
/* ์ ํธ๋ฆฌํฐ ํด๋์ค ํ๋๋ก๋ ๋ฎ์ด์ธ ์ ์์ด์ผ ํ๋ ์คํ์ผ */
}
@layer components {
/* ๐งฉ ์ค๊ฐ ์ฐ์ ์์ */
/* ์ฌ์ฌ์ฉ ์ปดํฌ๋ํธ ์คํ์ผ (.btn, .card, .prose ๋ฑ) */
/* @apply ๋ฅผ ์ฃผ๋ก ์ฐ๋ ๊ณต๊ฐ */
}
@layer utilities {
/* โก ๊ฐ์ฅ ๋์ ์ฐ์ ์์ */
/* ๋จ์ผ ์์ฑ ์ ํธ๋ฆฌํฐ ํด๋์ค๋ค */
/* ์ง์ ์ถ๊ฐํ ์ผ์ด ๊ฑฐ์ ์์ โ Tailwind ๊ฐ ์๋ ๊ด๋ฆฌ */
}์ค์ : @layer base ํ์ฉ
/* globals.css */
@import "tailwindcss";
@layer base {
/* ์ ์ญ ๊ธฐ๋ณธ ์คํ์ผ */
html {
scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* ๋ชจ๋ ์ด๋ฏธ์ง ๊ธฐ๋ณธ๊ฐ */
img, video {
max-width: 100%;
height: auto;
}
/* ์ปค์คํ
ํฌ์ปค์ค ์คํ์ผ ์ ์ญ ์ ์ฉ */
*:focus-visible {
outline: 2px solid theme('colors.brand.500');
outline-offset: 2px;
}
/* ํ๊ตญ์ด ํ
์คํธ ์ต์ ํ */
body {
word-break: keep-all;
overflow-wrap: break-word;
}
}์ค์ : @layer components ํ์ฉ
@layer components {
/* ๐ฏ ์ฌ๋ฐ๋ฅธ @apply ์ฌ์ฉ์ฒ โ ์จ๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์คํ์ผ ์ค๋ฒ๋ผ์ด๋ */
/* react-markdown ๋ณธ๋ฌธ ์คํ์ผ */
.prose-custom {
@apply text-base leading-relaxed text-gray-700 dark:text-gray-300;
}
.prose-custom h1 { @apply text-3xl font-bold text-gray-900 dark:text-white mb-4; }
.prose-custom h2 { @apply text-2xl font-semibold text-gray-800 dark:text-gray-100 mb-3; }
.prose-custom p { @apply mb-4; }
.prose-custom a { @apply text-brand-600 hover:text-brand-700 underline; }
.prose-custom code {
@apply rounded bg-gray-100 px-1.5 py-0.5 text-sm font-mono text-gray-800 dark:bg-gray-700 dark:text-gray-200;
}
.prose-custom pre {
@apply rounded-xl bg-gray-900 p-4 overflow-x-auto;
}
}๐ฏ @apply: ์ ํธ๋ฆฌํฐ๋ฅผ CSS ๋ก ์ถ์ถ
@apply ๋ Tailwind ์ ํธ๋ฆฌํฐ ํด๋์ค๋ฅผ CSS ๊ท์น ์์์ ์ฌ์ฉํ ์ ์๊ฒ ํด์ค.
์ฌ๋ฐ๋ฅธ @apply ์ฌ์ฉ ์๋๋ฆฌ์ค
์๋๋ฆฌ์ค 1: ์จ๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์คํ์ผ๋ง
/* react-select, react-datepicker ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ HTML ์ ์ง์ ์ ์ด ๋ถ๊ฐ */
@layer components {
/* DatePicker ์ปค์คํ
์คํ์ผ */
.react-datepicker__day--selected {
@apply bg-brand-600 text-white;
}
.react-datepicker__day:hover {
@apply bg-brand-100 text-brand-700;
}
}์๋๋ฆฌ์ค 2: ์ ์ญ input ์คํ์ผ (๋ฐ๋ณต ๋ฐฉ์ง)
@layer base {
/* ๋ชจ๋ input/select/textarea ์ ์ผ๊ด๋ ์คํ์ผ ์ ์ฉ */
input[type="text"],
input[type="email"],
input[type="password"],
select,
textarea {
@apply block w-full rounded-lg border border-gray-300 px-4 py-2.5 text-sm
placeholder:text-gray-400
focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-500/20
dark:border-gray-600 dark:bg-gray-800 dark:text-white dark:placeholder:text-gray-500;
}
}์๋๋ฆฌ์ค 3: ๋งํฌ๋ค์ด / ์ฝํ ์ธ ์์ญ
@layer components {
/* CMS ์ฝํ
์ธ ์์ญ โ HTML ํ๊ทธ์ ์ง์ ํด๋์ค๋ฅผ ๋ถ์ผ ์ ์๋ ๊ฒฝ์ฐ */
.content-area h1 { @apply text-3xl font-bold mb-6; }
.content-area h2 { @apply text-2xl font-semibold mb-4 mt-8; }
.content-area p { @apply text-base leading-relaxed text-gray-700 mb-4; }
.content-area ul { @apply list-disc list-inside mb-4 space-y-1; }
.content-area li { @apply text-gray-700; }
}๐ก @source: ์์ค ํ์ผ ๋ช ์
Tailwind v4 ๋ ์๋์ผ๋ก ์์ค ํ์ผ์ ๊ฐ์งํ์ง๋ง, ๋ช ๊ฐ์ง ๊ฒฝ์ฐ์ @source ๋ก ๋ช
์์ ์ผ๋ก ์ง์ ํด์ผ ํด.
@import "tailwindcss";
/* node_modules ์์ ์๋ UI ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํด๋์ค๋ ๋ฒ๋ค์ ํฌํจ */
@source "../node_modules/@my-company/ui-components/dist";
/* ๋์ ์ผ๋ก ์์ฑ๋๋ ํด๋์ค ํ์ผ */
@source "./templates/**/*.html";Tailwind ๊ฐ ํด๋์ค๋ฅผ ๊ฐ์ง ๋ชปํ ๋
// โ ๋ฌธ์ : ๋์ ์ผ๋ก ์์ฑ๋ ํด๋์ค๋ Tailwind ๊ฐ ๊ฐ์ง ๋ชปํด!
const color = 'blue';
const className = `bg-${color}-500`; // Tailwind ๊ฐ ์ด ํด๋์ค๋ฅผ ๋ฒ๋ค์์ ์ ๊ฑฐ!
// โ
ํด๊ฒฐ๋ฒ 1: ์ ์ฒด ํด๋์ค ์ด๋ฆ์ ์ ์ ์ผ๋ก ์ ์ธ
const colorMap = {
blue: 'bg-blue-500',
red: 'bg-red-500',
green: 'bg-green-500',
};
const className = colorMap[color];
// โ
ํด๊ฒฐ๋ฒ 2: safelist (v3) ๋๋ ๊ฐ์ ํฌํจ
// tailwind.config.js
module.exports = {
safelist: [
'bg-blue-500',
'bg-red-500',
'bg-green-500',
// ๋๋ ํจํด์ผ๋ก
{ pattern: /bg-(blue|red|green)-(500|600|700)/ },
],
};๐ป ์ค์ : Tailwind ์ ์ปค์คํ CSS ์ ๊ณต์กด
์์ฑ๋ globals.css ์์
/* app/globals.css */
@import "tailwindcss";
/* ===== ๋์์ธ ํ ํฐ ===== */
@theme {
--color-brand-500: #8b5cf6;
--color-brand-600: #7c3aed;
--color-brand-700: #6d28d9;
--font-sans: 'Pretendard Variable', -apple-system, sans-serif;
}
/* ===== Base Layer: ์ ์ญ ๊ธฐ๋ณธ ์คํ์ผ ===== */
@layer base {
html { scroll-behavior: smooth; }
body { word-break: keep-all; }
/* ์ ์ญ input ์คํ์ผ */
input[type="text"],
input[type="email"],
input[type="password"],
textarea {
@apply block w-full rounded-lg border border-gray-300 px-4 py-2.5 text-sm
focus:border-brand-500 focus:outline-none focus:ring-2 focus:ring-brand-500/20;
}
}
/* ===== Components Layer: ์ฌ์ฌ์ฉ ์ปดํฌ๋ํธ ===== */
@layer components {
/* ๋งํฌ๋ค์ด ์ฝํ
์ธ ์คํ์ผ */
.prose-korean {
@apply text-base leading-relaxed text-gray-700 dark:text-gray-300;
}
.prose-korean h1 { @apply text-3xl font-bold text-gray-900 dark:text-white mb-6 mt-0; }
.prose-korean h2 { @apply text-2xl font-semibold text-gray-800 dark:text-gray-100 mb-4 mt-8; }
.prose-korean p { @apply mb-4; }
.prose-korean code {
@apply rounded bg-gray-100 px-1.5 py-0.5 text-sm font-mono dark:bg-gray-700;
}
}๐จ @apply ๋จ์ฉ ๊ฒฝ๊ณ
์ ๋ ํ๋ฉด ์ ๋๋ @apply ํจํด
/* โ ์ด๋ ๊ฒ ํ๋ฉด Tailwind ๋ฅผ ์ฐ๋ ์ด์ ๊ฐ ์์ด์ง */
/* ์นด๋ ์ปดํฌ๋ํธ๋ฅผ @apply ๋ก ๋ง๋ ๋ค? */
.card {
@apply flex flex-col gap-3 rounded-xl border border-gray-200 bg-white p-5 shadow-md;
}
/* ๋ฒํผ์ @apply ๋ก ๋ง๋ ๋ค? */
.btn-primary {
@apply rounded-xl bg-brand-600 px-6 py-3 font-semibold text-white hover:bg-brand-700;
}์ ๋์๊ฐ?
1. Tailwind ์ ์ต๋ ์ฅ์ ์ธ co-location (์คํ์ผ์ด HTML ์์) ์ด ์ฌ๋ผ์ง
2. CSS ํ์ผ์ด ๋ค์ ๊ธธ์ด์ง๊ณ , BEM ์ฒ๋ผ ํ์ผ ๊ฐ ์ด๋์ด ์๊น
3. ์ด๋ด ๊ฑฐ๋ฉด ๊ทธ๋ฅ CSS Modules ์ฐ๋ ๊ฒ ๋์
4. hover:, md:, dark: ๊ฐ์ variant ๋ฅผ @apply ์์ ์ฐ๋ฉด ์์์น ๋ชปํ ๋์ ๊ฐ๋ฅ
์ฌ๋ฐ๋ฅธ ๋์:
// โ
React ์ปดํฌ๋ํธ๋ก ์ถ์ถ โ ์ด๊ฒ ์ง์ง ์ ๋ต
function Card({ children }: Props) {
return (
<div className="flex flex-col gap-3 rounded-xl border border-gray-200 bg-white p-5 shadow-md">
{children}
</div>
);
}
function Button({ children }: Props) {
return (
<button className="rounded-xl bg-brand-600 px-6 py-3 font-semibold text-white hover:bg-brand-700">
{children}
</button>
);
}@apply ์ฌ์ฉ ์ ํฉ์ฑ ํ๋จํ
| ์ํฉ | @apply ์ ํฉ? | ๋์ |
|---|---|---|
| ์จ๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค๋ฒ๋ผ์ด๋ | โ | ์์ |
| CMS/๋งํฌ๋ค์ด ์ฝํ ์ธ ์คํ์ผ | โ | ์์ |
| ์ ์ญ HTML ์์ ์คํ์ผ | โ | ์์ |
| ๋ฐ๋ณต๋๋ ์ปดํฌ๋ํธ ํด๋์ค | โ | React ์ปดํฌ๋ํธ ์ถ์ถ |
| ๋ฒํผ/์นด๋ ๊ฐ์ UI ์ปดํฌ๋ํธ | โ | React ์ปดํฌ๋ํธ ์ถ์ถ |
๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
| ๋๋ ํฐ๋ธ | ์ญํ | ์ฌ์ฉ ์๋๋ฆฌ์ค |
|---|---|---|
@layer base | HTML ๊ธฐ๋ณธ ์คํ์ผ | ์ ์ญ ํฐํธ, CSS reset, input ์คํ์ผ |
@layer components | ์ฌ์ฌ์ฉ ์ปดํฌ๋ํธ | ์จ๋ํํฐ ์ค๋ฒ๋ผ์ด๋, ๋งํฌ๋ค์ด ์คํ์ผ |
@layer utilities | ๋จ์ผ ์์ฑ ํด๋์ค | ์ปค์คํ ์ ํธ๋ฆฌํฐ (๊ฑฐ์ ์ฌ์ฉ ์ ํจ) |
@apply | CSS ์์์ Tailwind ์ฌ์ฉ | ์์ ์ฌ๋ฐ๋ฅธ ์๋๋ฆฌ์ค์์๋ง |
@source | ์์ค ํ์ผ ๋ช ์ | node_modules, ๋์ ๊ฒฝ๋ก |
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. ์์ฒ ์ด๊ฐ react-markdown ์ผ๋ก ๋ ๋๋ง๋ HTML ์ ์คํ์ผ๋งํด์ผ ํ๋ค. Tailwind ํด๋์ค๋ฅผ ์ง์ ๋ถ์ผ ์ ์๋๋ฐ, ์ฌ๋ฐ๋ฅธ ์ ๊ทผ๋ฒ์?
โ
์ ๋ต: @layer components ์ .prose-custom h1 { @apply ... } ์ฒ๋ผ ํด๋์ค ๊ธฐ๋ฐ CSS ๋ฅผ ์์ฑํ๋ค.
๐ก ์์ธ ํด์ค:
- ์จ๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(react-markdown, react-quill ๋ฑ)๊ฐ ์์ฑํ HTML ์๋ ์ง์ className ์ ์ถ๊ฐํ ์ ์์ด.
@layer components์ ๋ํผ ํด๋์ค(.prose-custom)๋ฅผ ๋ง๋ค๊ณ , ๊ทธ ์์์ ํ์ HTML ํ๊ทธ๋ค์@apply๋ก Tailwind ํด๋์ค๋ฅผ ์ ์ฉํด.@tailwindcss/typographyํ๋ฌ๊ทธ์ธ๋ ๊ฐ์ ์๋ฆฌ์ผ โproseํด๋์ค๋ฅผ ๋ํผ์ ๋ถ์ด๋ฉด ๋ด๋ถ HTML ์ด ์๋ฆ๋ต๊ฒ ์คํ์ผ๋ง๋ผ.- ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "์ง์ ๋ชป ๊ฑด๋๋ฆฌ๋ฉด
@layer components์์ ๋ถ๋ชจ-์์ ์ ํ์๋ก."
Q2. @layer base, @layer components, @layer utilities ์ค ์ด๋ ๋ ์ด์ด๊ฐ ์ฐ์ ์์๊ฐ ๊ฐ์ฅ ๋์๊ฐ?
โ
์ ๋ต: @layer utilities โ ๋์ค์ ์ ์๋ ๋ ์ด์ด์ผ์๋ก ์ฐ์ ์์๊ฐ ๋๋ค.
๐ก ์์ธ ํด์ค:
- CSS Cascade Layers ์ ์์น: ๋์ผํ ํน์ด๋(specificity)์์ ๋์ค์ ์ ์๋ ๋ ์ด์ด๊ฐ ์ด๊ฒจ.
- Tailwind ์ ๋ ์ด์ด ์์: base < components < utilities.
- ์ด ๋๋ถ์
@layer components์.card์คํ์ผ์@layer utilities์ ๋จ์ผ ํด๋์ค๋ก ๋ฎ์ด์์ธ ์ ์์ด. ํด๋์ค ์ถฉ๋ ๊ฑฑ์ ์์ด! - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "base โ components โ utilities. ๋ค๋ก ๊ฐ์๋ก ๊ฐํ๋ค."
Q3. ์์ฒ ์ด๊ฐ const cls = bg-$-500`` ์ฒ๋ผ ๋์ ํด๋์ค๋ฅผ ๋ง๋ค์๋๋ฐ ์คํ์ผ์ด ์ ์ฉ ์ ๋๋ค. ์ ๊ทธ๋ฐ๊ฐ?
โ ์ ๋ต: Tailwind ๋ ๋น๋ ์ ์์ค ํ์ผ์ ์ ์ ๋ถ์ํด์ ์ฌ์ฉ๋ ํด๋์ค๋ง ๋ฒ๋ค์ ํฌํจํ๋ค. ๋์ ์ผ๋ก ์กฐํฉ๋ ํด๋์ค ์ด๋ฆ์ ๊ฐ์งํ์ง ๋ชปํด์ ๋ฒ๋ค์์ ์ ๊ฑฐ๋๋ค.
๐ก ์์ธ ํด์ค:
- Tailwind ์ ๋น๋ ์ต์ ํ(Tree-Shaking)๋ ์์ค ํ์ผ์์ ์์ ํ ํด๋์ค ์ด๋ฆ ๋ฌธ์์ด์ ์ฐพ์.
`bg-${color}-500`์ ์์ ํ ์ด๋ฆ์ด ์๋๋ผ ๋์ ํํ์์ด๋ผ ๊ฐ์ง ๋ชปํด. - ํด๊ฒฐ์ฑ
: ์ ์ฒด ํด๋์ค ์ด๋ฆ์ ์ ์ ์ผ๋ก ๋ฏธ๋ฆฌ ์ ์ธ (
const map = { blue: 'bg-blue-500', ... }) ํ๊ฑฐ๋,safelist์ ์ง์ ๋ฑ๋ก. - ๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "Tailwind ๋ ์์ ํ ํด๋์ค ์ด๋ฆ์ ์ฐพ์. ๋ฐ๋ง ์์ด์ ์ ๋ณด์ธ๋ค."
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์ค๋ @apply ์ธ์ ์ฐ๊ณ ์ธ์ ์ฐ์ง ๋ง์์ผ ํ๋์ง๊ฐ ๋๋์ด ๋ช
ํํด์ก๋ค. ์จ๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ฑด๋๋ฆด ๋๋ CMS ์ฝํ
์ธ ์คํ์ผ๋ง ํ ๋ ๋ง๊ณ ๋ ์ฐ์ง ๋ง์ โ ์ด๊ฒ ์ค๋ ํต์ฌ์ด๋ค.
๊ทธ๋ฆฌ๊ณ ๋์ ํด๋์ค ์ด๋ฆ์ด ์ ์ ๋๋์ง๋ ์ดํด๋๋ค. Tailwind ๊ฐ ๋น๋ ํ์์ ์ ์ ๋ถ์์ ํ๋๋ฐ, `bg-${color}-500` ์ ๋ถ์์ ํผํด๊ฐ๋ ๊ฑฐ๋ผ์. ๋๋ ๋ชจ๋ฅด๊ฒ ์ด๊ฑธ ์ด ๊ณณ์ด ๋ช ๊ตฐ๋ฐ ์๋ ๊ฒ ๊ฐ์ผ๋๊น ๋ด์ผ ์ฝ๋ ๋ฆฌ๋ทฐ ๋ ํ๋ฒ ์ ๊ฒํด๋ด์ผ๊ฒ ๋ค.
๐ก ์ค๋์ ๊ตํ: "
@apply๋ '์ด์ฉ ์ ์์ ๋์ ํ์ถ๊ตฌ'๋ค. React ์ปดํฌ๋ํธ๋ฅผ ๋ง๋ค ์ ์๋ค๋ฉด ๊ทธ๊ฒ ํญ์ ๋จผ์ ๋ค."
์ฃผ๋ง์ ์์๋ค ์ปค๋ฎค๋ํฐ ๊ธ๋ก๋ฒ ์คํ์ผ ํ๋ฒ ์ ๋ฆฌํด์ผ๊ฒ ๋ค. ์ง๊ธ globals.css ๊ฐ ์ข ์ง์ ๋ถํ๋ฐ, @layer ๋ก ๊น๋ํ๊ฒ ๋ถ๋ฆฌํด์ ์ํธ ๋ ์นญ์ฐฌ ์ข ๋ฐ์์ผ์ง. ์ค๋์ ์ผ์ฐ ํด๊ทผ ์ฑ๊ณต! ์ง์์ ํธํ๊ฒ ์ฌ์ด์ผ์ง. ๐๏ธ