[HTML/CSS] Tailwind CSS ํด๋์ค ํจํด ์ฌํ์ฉ / ๊ธฐ๋ณธ ํ ๋ง ์์ &ํ์ฅ
๋ฐ๋ณต์ ์ผ๋ก ์ฌ์ฉํ๋ Tailwind CSS ํด๋์ค ํจํด์(ํน์ ์์์ ์ ์ํ ํด๋์ค ๊ทธ๋ฃน์ ์ฌ๋ฌ ๊ณณ์์ ์ฌ์ฉํ ๋) ํ๋์ ์ปค์คํ ํด๋์ค๋ก ์ถ์ถํด์ ์ฌ์ฉํ ์ ์๋ค
๋ฐฐ๊ฒฝ ์ง์ — Tailwind CSS 3๊ณ์ธต
Tailwind๋ CSS ์ฐ์ ์์๋ฅผ ์ ์ดํ๊ธฐ ์ํด base
, components
, utilities
3๊ฐ์ ๊ณ์ธต(layer)์ผ๋ก ๊ตฌ๋ถํ๋ค. ์ด๋ ๊ฒ ๊ณ์ธต์ ๊ตฌ๋ถํด๋๋ฉด ๊ฐ ์คํ์ผ์ด ์ด๋ป๊ฒ ์ํธ์์ฉํ๋์ง ๋ ์ฝ๊ฒ ์ดํดํ ์ ์์ผ๋ฉฐ, @layer
์ง์๋ฌธ(ํด๋น ์คํ์ผ์ด ์ด๋ค ๊ณ์ธต์ ์ํ ์ง ์ง์ )์ ์ฌ์ฉํด ์ํ๋ ๋ฐฉ์์ผ๋ก ์ ์ธ ์์๋ฅผ ์ ์ดํ ์ ์๊ฒ ๋๋ค. — ์ฐธ๊ณ
base
์ผ๋ฐ HTML ์์์ ์ ์ฉ๋๋ ์ฌ์ค์ ๊ท์น(reset rules) / ๊ธฐ๋ณธ ์คํ์ผ์ ์ํ ๋ ์ด์ด. base
๊ณ์ธต์ ํน์ ์๋ฆฌ๋จผํธ์ ๊ธฐ๋ณธ ์คํ์ผ์ ์ ์ํ๊ธฐ ์ ์ ํ๋ค.
/* styles/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
h1 {
@apply text-2xl;
}
h2 {
@apply text-xl;
}
/* ... */
}
components
์ ํธ๋ฆฌํฐ๋ฅผ ์ฌ์ฉํด ์ฌ์ ์ํ ์ ์๋, ํด๋์ค(class) ๊ธฐ๋ฐ ์คํ์ผ์ ์ํ ๋ ์ด์ด. ์๋๋ theme
ํจ์๋ฅผ ์ด์ฉํ ์์(์ฐธ๊ณ ). ๋ท๋
ธํ
์ด์
๋ฌธ๋ฒ์ผ๋ก Tailwind Config์ ์ ์ํด๋ ๊ฐ์ ์ ๊ทผํ ์ ์๋ค.
/* styles/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.card {
background-color: theme("colors.white"); /* Tailwind config ๊ฐ ์ ๊ทผ */
border-radius: theme("borderRadius.lg");
padding: theme("spacing.6");
box-shadow: theme("boxShadow.xl");
}
/* ... */
}
์จ๋ํํฐ ์ปดํฌ๋ํธ์์ ์ ๊ณตํ๋ ํด๋์ค๋ฅผ ์์ ํ ๋๋ components
๊ณ์ธต์ ์ ์ํ๋ฉด ์ข๋ค
/* styles/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.select2-dropdown {
@apply rounded-b-lg shadow-md;
}
.select2-search {
@apply border border-gray-300 rounded;
}
.select2-results__group {
@apply text-lg font-bold text-gray-900;
}
/* ... */
}
utilities
๋ค๋ฅธ ์คํ์ผ๋ณด๋ค ์ฐ์ ์๋๋ ๋จ์ผ ๋ชฉ์ ํด๋์ค๋ฅผ ์ํ ๋ ์ด์ด. utilities
๊ณ์ธต์ Tailwind๊ฐ ์ ๊ณตํ์ง ์๋ ์ปค์คํ
์ ํธ๋ฆฌํฐ(ํด๋์ค)๋ฅผ ์ ์ํ๊ธฐ ์ข๋ค.
/* styles/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer utilities {
.content-auto {
content-visibility: auto;
}
}
Tailwind์ ํ๋ฌ๊ทธ์ธ ์์คํ ์ ์ด์ฉํด ์ ํธ๋ฆฌํฐ ํด๋์ค๋ฅผ ์ ์ํ ์๋ ์๋ค.
// tailwind.config.js
const plugin = require('tailwindcss/plugin');
module.exports = {
// ...
plugins: [
plugin(function ({ addUtilities }) {
const newUtilities = {
'.content-auto': {
'content-visibility': 'auto',
},
};
addUtilities(newUtilities);
}),
],
};
๐๏ธ ์ปค์คํ
์คํ์ผ์ ์ ์ํ ๋ @layer
์ง์๋ฌธ์ ์ฌ์ฉํ๋ฉด, @tailwind
์ง์๋ฌธ์ผ๋ก ์๋ ์ฌ๋ฐฐ์น๋ผ์ ์ ์ธ ์์๋ฅผ ์ ์ดํ๋๋ฐ ๋์์ ์ฃผ๋ฉฐ, ์ปค์คํ
์คํ์ผ์๋ modifiers ๋ฐ tree shaking ๊ธฐ๋ฅ์ด ์ ์ฉ๋๋ค.
- Modifiers ๊ธฐ๋ฅ : hover, responsive breakpoints, dark mode ๋ฑ์ ํธ๋ค๋งํ๋ Tailwind ๋ฌธ๋ฒ
ex)hover:bg-sky-700
,lg:dark:content-auto
- Tree shaking ๊ธฐ๋ฅ : ์ฌ์ฉํ์ง ์์ ํด๋์ค๋ compiled CSS์ ํฌํจ๋์ง ์๋ ๊ธฐ๋ฅ.
@layer
์ง์๋ฌธ ์์ด ์ ์ํ ์ปค์คํ ์คํ์ผ์ ํญ์ compiled CSS์ ํฌํจ๋จ
ํด๋์ค ํจํด ์ถ์ถ
@apply ์ง์๋ฌธ ์ฌ์ฉ
๐ก ๊ณต์๋ฌธ์์์ @apply
์ง์๋ฌธ(directive)์ ์ฌ์ฉํ ํด๋์ค ์ถ์ํ๋ form ์กฐ์์ด๋ button ์ฒ๋ผ “์ฌ์ฌ์ฉ์ฑ์ด ๋์ ์์ ๋ถ๋ถ”์ ๋ํด์๋ง ์ฌ์ฉํ ๊ฒ์ ๊ถ๊ณ ํ๋ค. @apply
๋ฅผ ์ฌ์ฉํ๋ฉด CSS ๋ฒ๋ค ํฌ๊ธฐ๊ฐ ์ปค์ง๊ณ ํด๋์ค ์ด๋ฆ์ ๊ณ ๋ คํด์ผ ๋๋ ๋ฑ Tailwind ํ๋ ์์ํฌ๊ฐ ๊ฐ์ง ์ฅ์ ์ ์์ํ๊ธฐ ๋๋ฌธ์ด๋ค — ์ฐธ๊ณ
@apply
์ง์๋ฌธ์ ์ปค์คํ
์คํ์ผๅ
์ ํธ๋ฆฌํฐ ํด๋์ค๋ฅผ ์ ์ฉํ ๋ ์ฌ์ฉํ๋ค. โ@apply
์ง์๋ฌธ์ผ๋ก ์์ฃผ ์ฌ์ฉํ๋ ํด๋์ค ํจํด์ ์ถ์ถํด ์๋ก์ด ์ปค์คํ
ํด๋์ค๋ฅผ ๋ง๋ค๊ฑฐ๋, โ๊ธฐ์กด ํด๋์ค(์จ๋ํํฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ด๋ฏธ ์ ์ํด๋ ํด๋์ค ๋ฑ)์ ์๋ก์ด ์คํ์ผ์ ๋ฎ์ด์ธ ์ ์๋ค.
@apply
์ง์๋ฌธ ์์ ์์ฑํ ํด๋์ค ์คํ์ผ์ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ชจ๋ !important
์์ฑ์ ์ ๊ฑฐํ๋ค. — ์ฐธ๊ณ
/* styles/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn-confirm {
/* ํ์ธ ๋ฒํผ์ ๋ํ ์ปค์คํ
ํด๋์ค */
@apply bg-blue-500 text-white py-2 px-4 rounded hover:bg-blue-600;
}
.btn-cancel {
/* ์ทจ์ ๋ฒํผ์ ๋ํ ์ปค์คํ
ํด๋์ค */
@apply bg-transparent hover:bg-gray-500 text-blue-700 hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded;
/* @apply btn-confirm ... -> ๋ค๋ฅธ ์ปค์คํ
ํด๋์ค๋ฅผ ์ ์ฉํ ์๋ ์๋ค */
}
}
<!-- Button.tsx -->
<!-- index.css์ ์ ์ํ ์ปค์คํ
ํด๋์ค ์ฌ์ฉ -->
<button type="button" className="btn-confirm">ํ์ธ</button>
<button type="button" className="btn-confirm">์ทจ์</button>
ํ๋ฌ๊ทธ์ธ ์์คํ ์ฌ์ฉ
Tailwind์ ํ๋ฌ๊ทธ์ธ ์์คํ ์ ์ฌ์ฉํ๋ฉด ์คํ์ผ ์ํธ์ ์ฝ์ ๋ ์๋ก์ด ์คํ์ผ์ ์๋ฐ์คํฌ๋ฆฝํธ๋ฅผ ์ด์ฉํด์ ๋ฑ๋กํ ์ ์๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก ์๋์ฒ๋ผ ์ฌ์ฉํ๋ค. — plugin ํจ์์ ๊ฐ ํ๋ผ๋ฏธํฐ ์ค๋ช
// tailwind.config.js
const plugin = require('tailwindcss/plugin');
module.exports = {
// ...
plugins: [
plugin(function ({ addUtilities, addComponents, e, prefix, config }) {
// addUtilities({ ... }) -> for registering new static utility styles (utilities layer)
// addComponents({ ... }) -> for registering new static component styles (components layer)
// ...
}),
],
};
components
๊ณ์ธต(layer)์ ๋ฑ๋กํ ์คํ์ผ์ด๋ฏ๋ก addComponents
์ ํด๋์ค๋ช
๊ณผ ์คํ์ผ์ ์ ์ํ๋ค. ์์ฑ๋ช
์ camelCase
๋ฅผ ์ฌ์ฉํ๊ณ ์์ฑ ๊ฐ์ theme ํจ์๋ฅผ ์ด์ฉํ๊ฑฐ๋, '...'
๋ฌธ์์ด ํํ๋ก ์
๋ ฅํ๋ค.
๐ก theme
ํจ์์์ Tailwind config ๊ฐ์ ์ ๊ทผํ ๋ -
๋์๊ฐ ์๋ .
์จ์ ์ ์ฌ์ฉํ๋ค. ์ซ์ 2.5
๊ฐ์ .
์จ์ ์ด ๋ค์ด๊ฐ ๊ฐ์ ์ ์ ๋ Braket Notation [...]
์ ์ฌ์ฉํ๋ค.
background-color: theme('colors.blue.500');
height: calc(100vh - theme('spacing[2.5]'));
// tailwind.config.js
const plugin = require('tailwindcss/plugin');
module.exports = {
// ...
plugins: [
plugin(function ({ addComponents, theme }) {
addComponents({
'.btn-confirm': {
borderRadius: theme('spacing.1'), // '0.25rem'
backgroundColor: theme('colors.blue.500'), // '#3b82f6'
color: theme('color.white'), // '#fff'
padding: `${theme('spacing.2')} ${theme('spacing.4')}`, // '0.5rem 1rem 0.5rem 1rem'
'&:hover': {
backgroundColor: theme('colors.blue.600'), // '#2563eb'
},
},
});
}),
],
};
๐๏ธ ์์ ๋์ผํ ๋ฐฉ๋ฒ์ผ๋ก tailwind.config.js ํ์ผ์ ์ ํธ๋ฆฌํฐ ํด๋์ค๋ฅผ ๋ฑ๋กํ ์๋ ์๋ค.
๊ธฐ๋ณธ ํ ๋ง ์์ / ํ์ฅ
Tailwind์์ ๋ฒ์ฉ์ ์ผ๋ก ์ฌ์ฉํ ์ ์๋ default theme๋ฅผ ์ ๊ณตํ๋ค. ์ํ๋ค๋ฉด tailwind.config.js์ theme
์์ฑ์์ ๊ธฐ๋ณธ ํ
๋ง๋ฅผ ์์ ํ๊ฑฐ๋ ํ์ฅํ ์๋ ์๋ค. theme
์์ฑ์ screens
, colors
, spacing
๋ฑ์ key๊ฐ ํฌํจ๋์ด ์๋ค. ์ ์ฒด key ๋ฆฌ์คํธ๋ ๋งํฌ์์ ํ์ธํ ์ ์๋ค.
// tailwind.config.js - ๊ธฐ๋ณธ ํ
๋ง ํ์ฅ
module.exports = {
theme: {
extend: {
// Adds a new breakpoint in addition to the default breakpoints
screens: {
'3xl': '1600px',
},
},
},
};
๊ธฐ๋ณธ ํ
๋ง๋ฅผ ์์ ํ ๋ ๋ช
์ํ์ง ์์ key๋ ๊ธฐ๋ณธ ํ
๋ง๋ฅผ ๊ทธ๋๋ก ์์๋ฐ๋๋ค. ์๋์์ opacity
(ํฌ๋ช
๋) key๋ง ์์ ํ์ผ๋ฏ๋ก colors
, spacing
, border-radius
๋ฑ์ ๋ํ ๊ธฐ๋ณธ ํ
๋ง ๊ตฌ์ฑ์ ๋ชจ๋ ์ ์ง๋๋ค. — ์ฐธ๊ณ
Any keys you do not provide will be inherited from the default theme
// tailwind.config.js - ๊ธฐ๋ณธ ํ
๋ง ์์
module.exports = {
theme: {
// Replaces all of the default `opacity` values
opacity: {
0: '0',
20: '0.2',
40: '0.4',
60: '0.6',
80: '0.8',
100: '1',
},
},
};
theme()
ํจ์๋ ๋ค๋ฅธ config ๊ฐ์ ์ ๊ทผํ ์ ์์ผ๋ฏ๋ก ํ
๋ง๋ฅผ ์์ ํ ๋๋ ์ฌ์ฉํ ์ ์๋ค. — ์ฐธ๊ณ
module.exports = {
theme: {
fill: ({ theme }) => ({
gray: theme('colors.gray'),
}),
},
};
์ธํผ๋ํฐ์ฐ์ค Regular๋ฅผ ๊ธฐ๋ณธ ํฐํธ๋ก, ์ธํผ๋ํฐ์ฐ์ค Bold๋ฅผ <h1>
ํ๊ทธ ํฐํธ๋ก ๋ฐ๊พธ๋ ์์.
/* styles/index.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
/* @font-face ์ง์๋ฌธ์ผ๋ก ์๋ก ์ถ๊ฐํ ํฐํธ ๋ค์ด๋ก๋ */
@font-face {
font-family: 'InfinitySans-RegularA1';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@2.1/InfinitySans-RegularA1.woff')
format('woff');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'InfinitySans-BoldA1';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@2.1/InfinitySans-BoldA1.woff')
format('woff');
font-weight: normal;
font-style: normal;
}
body {
/* tailwind.config.js์์ ์์ ํ fontFamily.sans ์์ฑ์ ๊ธฐ๋ณธ ํฐํธ๋ก ์ง์ */
@apply font-sans;
}
h1 {
/* tailwind.config.js์์ ์ถ๊ฐํ fontFamily.heading ์์ฑ์ ๊ธฐ๋ณธ ํฐํธ๋ก ์ง์ */
@apply font-heading;
}
}
// tailwind.config.js - ๊ธฐ๋ณธ ํ
๋ง ํ์ฅ ์์
const { fontFamily } = require('tailwindcss/defaultTheme'); // ๊ธฐ๋ณธ ํ
๋ง์ fontFamily ์์ฑ import
module.exports = {
content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
extend: {
fontFamily: {
// fontFamily.sans ์์ฑ ๋ณ๊ฒฝ(regular ๊ตต๊ธฐ). font-sans ํด๋์ค๋ก ์ฌ์ฉ
// fontFamily ์์ฑ์ ๊ธฐ๋ณธ์ ์ผ๋ก sans, mono, seif ์์ฑ์ ํฌํจํจ
sans: ['InfinitySans-RegularA1', ...fontFamily.sans],
// fontFamily.heading ์์ฑ ์ถ๊ฐ(bold ๊ตต๊ธฐ). font-heading ํด๋์ค๋ก ์ฌ์ฉ
heading: ['InfinitySans-BoldA1', ...fontFamily.sans],
},
keyframes: {
blink: { '50%': { opacity: '0' } },
},
animation: {
blink: 'blink 1s step-end infinite',
},
colors: {
'primary-blue': '#464ea3',
'primary-red': '#f2542d',
// ...
},
screens: {
ss: '450px', // @media (min-width: 450px) {...}
// ...
},
},
},
plugins: [],
};
๋ ํผ๋ฐ์ค
๊ธ ์์ ์ฌํญ์ ๋ ธ์ ํ์ด์ง์ ๊ฐ์ฅ ๋น ๋ฅด๊ฒ ๋ฐ์๋ฉ๋๋ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํด ์ฃผ์ธ์
'๐ช Programming' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๋๊ธ
์ด ๊ธ ๊ณต์ ํ๊ธฐ
-
๊ตฌ๋
ํ๊ธฐ
๊ตฌ๋ ํ๊ธฐ
-
์นด์นด์คํก
์นด์นด์คํก
-
๋ผ์ธ
๋ผ์ธ
-
ํธ์ํฐ
ํธ์ํฐ
-
Facebook
Facebook
-
์นด์นด์ค์คํ ๋ฆฌ
์นด์นด์ค์คํ ๋ฆฌ
-
๋ฐด๋
๋ฐด๋
-
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
๋ค์ด๋ฒ ๋ธ๋ก๊ทธ
-
Pocket
Pocket
-
Evernote
Evernote
๋ค๋ฅธ ๊ธ
-
[React] ๋ฆฌ์กํธ Strict Mode ์๊ฒฉ ๋ชจ๋์ ์ฌ์ด๋ ์ดํํธ
[React] ๋ฆฌ์กํธ Strict Mode ์๊ฒฉ ๋ชจ๋์ ์ฌ์ด๋ ์ดํํธ
2024.05.05 -
[React] ํค๋ณด๋๋ก ์กฐ์ํ ์ ์๋ ๋๋กญ๋ค์ด ์๋์์ฑ ๊ฒ์์ฐฝ ๊ตฌํํ๊ธฐ
[React] ํค๋ณด๋๋ก ์กฐ์ํ ์ ์๋ ๋๋กญ๋ค์ด ์๋์์ฑ ๊ฒ์์ฐฝ ๊ตฌํํ๊ธฐ
2024.05.05 -
[React] ๋ฆฌ์กํธ ๋๋๊ทธ์ค๋๋กญ ํ์ผ ์ ๋ก๋ ๊ตฌํ
[React] ๋ฆฌ์กํธ ๋๋๊ทธ์ค๋๋กญ ํ์ผ ์ ๋ก๋ ๊ตฌํ
2024.05.05 -
[HTML/CSS] width ์์ฑ ์๋ ๋งค์ปค๋์ฆ
[HTML/CSS] width ์์ฑ ์๋ ๋งค์ปค๋์ฆ
2024.05.05