๋ฐ˜์‘ํ˜•

๋ฐ˜๋ณต์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” 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: [],
};

 

๋ ˆํผ๋Ÿฐ์Šค


Reusing Styles - Tailwind CSS  

 


๊ธ€ ์ˆ˜์ •์‚ฌํ•ญ์€ ๋…ธ์…˜ ํŽ˜์ด์ง€์— ๊ฐ€์žฅ ๋น ๋ฅด๊ฒŒ ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค. ๋งํฌ๋ฅผ ์ฐธ๊ณ ํ•ด ์ฃผ์„ธ์š”
๋ฐ˜์‘ํ˜•