๐ŸŽจ Tailwind Advanced 3์žฅ: ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ ์‹ฌํ™” โ€” ๊ฐ€๋…์„ฑ๊ณผ ์‹œ๊ฐ ์œ„๊ณ„์˜ ๊ธฐ์ˆ 

2026๋…„ 3์›” 5์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

line-clamp, text-balance, font-variant-numeric, @tailwindcss/typography ํ”Œ๋Ÿฌ๊ทธ์ธ, fluid typography๊นŒ์ง€ โ€” ํ…์ŠคํŠธ๋ฅผ ์™„์ „ํžˆ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


๐Ÿ“Œ ์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ธฐ ์ „์—

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 28๋ถ„ (์ „์ฒด) / ํ•ต์‹ฌ ํŒŒํŠธ๋งŒ: 14๋ถ„

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ๋ฐฐ๊ฒฝ ์„ธ๊ณ„๊ด€: '์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ'

  • ์˜์ˆ™(๋””์ž์ด๋„ˆ): "์˜์ฒ  ๋‹˜! ์Šคํ„ฐ๋”” ์นด๋“œ ๋””์ž์ธ ํ™•์ธํ•ด๋ดค๋Š”๋ฐ์š”, ์นด๋“œ ํƒ€์ดํ‹€์ด ์งง์€ ๊ฑด 1์ค„, ๊ธด ๊ฑด 3์ค„๊นŒ์ง€ ๋Š˜์–ด๋‚˜์„œ ์นด๋“œ ๋†’์ด๊ฐ€ ์ œ๊ฐ๊ฐ์ด์—์š”. ์นด๋“œ ๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ์ด ์—‰๋ง์ด ๋˜๊ณ  ์žˆ์–ด์š”. 2์ค„๋กœ ๊ณ ์ •ํ•ด ์ฃผ์„ธ์š”!"
  • ์˜์ฒ (์‹ ์ž…): "์•„, ๊ทธ๊ฑฐ์š”. ์ œ๊ฐ€ JavaScript๋กœ ๋ฌธ์ž์—ด ๊ธธ์ด ์ฒดํฌํ•ด์„œ ์ผ์ • ๊ธธ์ด ๋„˜์œผ๋ฉด ...์œผ๋กœ ์ž๋ฅด๋Š” ํ•จ์ˆ˜ ๋งŒ๋“ค๋ฉด ๋˜๊ฒ ๋Š”๋ฐ์š”? ์•„๋‹ˆ๋ฉด CSS white-space: nowrap; overflow: hidden; text-overflow: ellipsis; ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด 1์ค„ ์ž๋ฅด๊ธฐ๋Š” ๋˜๋Š”๋ฐ, 2์ค„์€..."
  • ์˜ํ˜ธ(๋ฆฌ๋“œ): "์˜์ฒ  ๋‹˜. line-clamp-2 ํ•˜๋‚˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์‹ฌ์ง€์–ด ... ๋ง์ค„์ž„ํ‘œ๋„ ์ž๋™์œผ๋กœ ๋ถ™์–ด์š”."

๐ŸŽฏ ์ด ๋ฌธ์„œ๋ฅผ ๋‹ค ์ฝ์œผ๋ฉด ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • line-clamp-* ์˜ ๋‚ด๋ถ€ ๊ตฌํ˜„ ์›๋ฆฌ๋ฅผ ์ดํ•ดํ•˜๊ณ , truncate ์™€์˜ ์ฐจ์ด๋ฅผ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • text-balance, text-pretty ๋กœ ์ œ๋ชฉ๊ณผ ๋ณธ๋ฌธ์˜ ์ค„๋ฐ”๊ฟˆ ํ’ˆ์งˆ์„ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • tabular-nums, slashed-zero ๋“ฑ์œผ๋กœ ๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ”์˜ ์ˆซ์ž ์ •๋ ฌ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค.
  • prose ํ”Œ๋Ÿฌ๊ทธ์ธ์œผ๋กœ ๋งˆํฌ๋‹ค์šด ๋ Œ๋”๋ง ์˜์—ญ์„ ์ฆ‰์‹œ ์Šคํƒ€์ผ๋งํ•  ์ˆ˜ ์žˆ๋‹ค.
  • clamp() ํ•จ์ˆ˜๋กœ Fluid Typography๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ์›๋ฆฌ๋ฅผ ์ดํ•ดํ•œ๋‹ค.

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ํ๋ฆ„
์˜์ˆ™์ด์˜ ์นด๋“œ ๋ ˆ์ด์•„์›ƒ ๋ฌธ์ œ โ†’ line-clamp ๋งˆ์Šคํ„ฐ โ†’ text-balance โ†’ ์ˆซ์ž ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ โ†’ prose ํ”Œ๋Ÿฌ๊ทธ์ธ โ†’ fluid typography โ†’ ์‹ค์ „ ๊ตฌํ˜„


๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€

ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ๋Š” UI์—์„œ ๊ฐ€์žฅ ๊ณผ์†Œํ‰๊ฐ€๋˜๋Š” ๋ถ„์•ผ์•ผ. ๋Œ€๋ถ€๋ถ„์˜ ๊ฐœ๋ฐœ์ž๋Š” font-size์™€ font-weight ์ •๋„๋กœ ์ถฉ๋ถ„ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•˜๊ฑฐ๋“ .

ํ•˜์ง€๋งŒ ์‹ค๋ฌด์—์„œ ๋””์ž์ด๋„ˆ์™€ ํ•จ๊ป˜ ์ผํ•ด๋ณธ ๊ฒฝํ—˜์ด ์žˆ๋‹ค๋ฉด, ์ด๋Ÿฐ QA ์ด์Šˆ๋“ค์„ ํ•œ ๋ฒˆ์ฏค ๋งˆ์ฃผ์ณค์„ ๊ฑฐ์•ผ:

  • ์นด๋“œ ์ œ๋ชฉ ๊ธธ์ด๊ฐ€ ๋‹ฌ๋ผ์„œ ๊ทธ๋ฆฌ๋“œ ์ •๋ ฌ์ด ๋ฌด๋„ˆ์ง
  • ํฐ ํ™”๋ฉด์—์„œ h1 ์ œ๋ชฉ์ด ๋งˆ์ง€๋ง‰ ๋‹จ์–ด ํ•˜๋‚˜๋งŒ ํ˜ผ์ž 2๋ฒˆ์งธ ์ค„์— ๋œ๋  ์žˆ์Œ
  • ๋Œ€์‹œ๋ณด๋“œ์˜ ์ˆซ์ž ์ปฌ๋Ÿผ์—์„œ ์ˆซ์ž ํญ์ด ๋‹ฌ๋ผ์„œ ์†Œ์ˆ˜์ ์ด ์•ˆ ๋งž์Œ
  • ๋ธ”๋กœ๊ทธ ๋งˆํฌ๋‹ค์šด ์ฝ˜ํ…์ธ ์— ์Šคํƒ€์ผ์ด ์ „ํ˜€ ์—†์–ด์„œ ๋‚ ๊ฒƒ์˜ HTML๋งŒ ๋ณด์ž„

์ด ๋ชจ๋“  ๊ฒƒ๋“ค์ด CSS ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ ์†์„ฑ ํ•˜๋‚˜ํ•˜๋‚˜๋กœ ํ•ด๊ฒฐ๋ผ. ๊ทธ๋ฆฌ๊ณ  Tailwind๋Š” ์ด๋ฅผ ์ง๊ด€์ ์ธ ์œ ํ‹ธ๋ฆฌํ‹ฐ๋กœ ์ถ”์ƒํ™”ํ•ด๋†จ์–ด.


๐Ÿ—๏ธ ๋น„์œ ๋กœ ๋จผ์ € ์ดํ•ดํ•˜๊ธฐ

๐Ÿ“ฐ ์‹ ๋ฌธ ํŽธ์ง‘์ž์˜ ๋ ˆ์ด์•„์›ƒ ์ž‘์—…

์‹ ๋ฌธ์‚ฌ ํŽธ์ง‘์ž๋Š” ๊ธฐ์‚ฌ๋ฅผ ๋ฐ›์œผ๋ฉด ํŽ˜์ด์ง€์— ์ •ํ™•ํžˆ ๋งž๋„๋ก ์ž๋ฅด๊ณ  ์ •๋ ฌํ•ด์•ผ ํ•ด.

  • line-clamp: ๊ธฐ์‚ฌ๊ฐ€ ๋„ˆ๋ฌด ๊ธธ๋ฉด ์ •ํ•ด์ง„ ์ค„ ์ˆ˜์—์„œ "..." ์œผ๋กœ ์ž๋ฅด๋Š” ๊ธฐ์ˆ . ํŽธ์ง‘์ž๊ฐ€ "3๋‹จ๋ฝ๊นŒ์ง€๋งŒ 1๋ฉด์— ๋„ฃ์–ด" ํ•˜๋Š” ๊ฒƒ.
  • text-balance: ์ œ๋ชฉ์ด ํ•œ ์ค„์— ๋”ฑ ๋งž์ง€ ์•Š์„ ๋•Œ ์ค„๋ฐ”๊ฟˆ์„ ๊ท ํ˜• ์žˆ๊ฒŒ ๋‚˜๋ˆ„๋Š” ๊ธฐ์ˆ . "์ œ๋ชฉ์ด ๋์—์„œ ๋‹จ์–ด ํ•˜๋‚˜๋งŒ ๋–จ์–ด์ง€์ง€ ์•Š๊ฒŒ ํ•ด์ค˜."
  • tabular-nums: ์žฌ๋ฌด ๋ณด๋„์—์„œ ๊ธˆ์•ก ์ปฌ๋Ÿผ์˜ ์ˆซ์ž ํญ์„ ์ผ์ •ํ•˜๊ฒŒ ๋งž์ถฐ์„œ ์†Œ์ˆ˜์ ์ด ์„ธ๋กœ๋กœ ์ •๋ ฌ๋˜๊ฒŒ ํ•˜๋Š” ๊ธฐ์ˆ .
  • prose: ๊ธฐ์‚ฌ ๋ณธ๋ฌธ์— ์ž๋™์œผ๋กœ ์‹ ๋ฌธ ์Šคํƒ€์ผ(์ œ๋ชฉ ํฌ๊ธฐ, ๋ฌธ๋‹จ ๊ฐ„๊ฒฉ, ์ธ์šฉ๊ตฌ ์Šคํƒ€์ผ)์„ ์ž…ํžˆ๋Š” ์Šคํƒ€์ผ ์‹œํŠธ.

โœ‚๏ธ 1๋‹จ๊ณ„: line-clamp โ€” ์ค„ ์ˆ˜ ๊ณ ์ •์˜ ๊ธฐ์ˆ 

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • line-clamp-* ์œ ํ‹ธ๋ฆฌํ‹ฐ๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ์–ด๋–ค CSS๋ฅผ ์ƒ์„ฑํ•˜๋Š”์ง€ ์ดํ•ดํ•œ๋‹ค.
  • truncate (1์ค„ ์ž๋ฅด๊ธฐ)์™€ line-clamp-* (N์ค„ ์ž๋ฅด๊ธฐ)์˜ ์ฐจ์ด๋ฅผ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„ํ•œ๋‹ค.

truncate ์™€ line-clamp ์˜ ์ฐจ์ด

๋‘ ์œ ํ‹ธ๋ฆฌํ‹ฐ๋Š” "๋„˜์น˜๋Š” ํ…์ŠคํŠธ๋ฅผ ์ž๋ฅธ๋‹ค"๋Š” ๋ชฉ์ ์€ ๊ฐ™์ง€๋งŒ, ์ž‘๋™ ๋ฐฉ์‹์ด ์™„์ „ํžˆ ๋‹ฌ๋ผ.

<!-- truncate: 1์ค„ ๊ฐ•์ œ + overflow ์ˆจ๊น€ + ... ํ‘œ์‹œ -->
<!-- ์‚ฌ์šฉํ•˜๋Š” CSS:
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;  โ† ์ด๊ฒŒ ํ•ต์‹ฌ! ์ค„๋ฐ”๊ฟˆ์„ ์™„์ „ํžˆ ๋ง‰์Œ
-->
<p class="truncate w-64">
  ์ด ํ…์ŠคํŠธ๋Š” ๋ฌด์กฐ๊ฑด 1์ค„๋กœ๋งŒ ํ‘œ์‹œ๋˜๊ณ  ๋‚˜๋จธ์ง€๋Š” ...์œผ๋กœ ์ž˜๋ ค
</p>
 
<!-- line-clamp-N: N์ค„๊นŒ์ง€๋งŒ ํ—ˆ์šฉ + overflow ์ˆจ๊น€ + ... ํ‘œ์‹œ -->
<!-- ์‚ฌ์šฉํ•˜๋Š” CSS:
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: N;  โ† N์ค„๊นŒ์ง€ ํ—ˆ์šฉ!
-->
<p class="line-clamp-2 w-64">
  ์ด ํ…์ŠคํŠธ๋Š” 2์ค„๊นŒ์ง€๋Š” ๋ณด์—ฌ์ค˜.
  3๋ฒˆ์งธ ์ค„๋ถ€ํ„ฐ๋Š” ์ˆจ๊ธฐ๊ณ  ...์œผ๋กœ ํ‘œ์‹œํ•ด.
  ๊ธด ํ…์ŠคํŠธ๊ฐ€ ๊ณ„์†๋˜์–ด๋„ 2์ค„์—์„œ ๋ฉˆ์ถฐ.
</p>

์‹ค์ „: ์นด๋“œ ๊ทธ๋ฆฌ๋“œ ๋ ˆ์ด์•„์›ƒ ๊ณ ์ •

// components/StudyCard.tsx
 
// โŒ ์˜์ฒ ์ด์˜ ์ดˆ๊ธฐ ์ฝ”๋“œ: ์ œ๋ชฉ ๊ธธ์ด์— ๋”ฐ๋ผ ์นด๋“œ ๋†’์ด๊ฐ€ ๋‹ฌ๋ผ์ง
export function NaiveStudyCard({ title, description }) {
  return (
    <div className="bg-white rounded-xl p-4 shadow">
      {/* ๐Ÿฃ ์˜์ฒ : ์ œ๋ชฉ์ด 1์ค„์ผ ๋•Œ๋Š” ์นด๋“œ๊ฐ€ ์ž‘๊ณ , 3์ค„์ผ ๋•Œ๋Š” ์ปค์„œ ๊ทธ๋ฆฌ๋“œ๊ฐ€ ๊นจ์ง */}
      <h3 className="font-semibold text-gray-900">{title}</h3>
      <p className="text-gray-600 text-sm mt-2">{description}</p>
    </div>
  );
}
 
// โœ… ์˜ํ˜ธ์˜ ๋ฆฌํŒฉํ† ๋ง: line-clamp์œผ๋กœ ์นด๋“œ ๋†’์ด ๊ณ ์ •
export function StudyCard({ title, description, memberCount, topic }) {
  return (
    <div className="bg-white rounded-xl p-4 shadow flex flex-col">
      {/* ํ† ํ”ฝ ํƒœ๊ทธ */}
      <span className="text-xs font-medium text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-full w-fit">
        {topic}
      </span>
 
      {/* ์ œ๋ชฉ: ์ตœ๋Œ€ 2์ค„, ๋„˜์œผ๋ฉด ... */}
      <h3 className="
        font-semibold text-gray-900 mt-2
        line-clamp-2      /* 2์ค„ ์ดํ›„ ๋ง์ค„์ž„ํ‘œ */
        /* ๋‚ด๋ถ€์ ์œผ๋กœ: display: -webkit-box + -webkit-line-clamp: 2 */
      ">
        {title}
      </h3>
 
      {/* ์„ค๋ช…: ์ตœ๋Œ€ 3์ค„ */}
      <p className="text-gray-600 text-sm mt-2 line-clamp-3 flex-1">
        {description}
      </p>
 
      {/* ํ•˜๋‹จ ๋ฉ”ํƒ€ ์ •๋ณด (ํ•ญ์ƒ ์นด๋“œ ํ•˜๋‹จ์— ๊ณ ์ •) */}
      <div className="mt-4 pt-3 border-t border-gray-100 flex justify-between items-center">
        <span className="text-sm text-gray-500">๋ฉค๋ฒ„ {memberCount}๋ช…</span>
        <button className="text-sm font-medium text-indigo-600 hover:text-indigo-700">
          ์ฐธ์—ฌํ•˜๊ธฐ โ†’
        </button>
      </div>
    </div>
  );
}

line-clamp ํ•ด์ œ: ๋ฐ˜์‘ํ˜• ์ ์šฉ

<!-- ๋ชจ๋ฐ”์ผ์—์„œ๋Š” 3์ค„, ๋ฐ์Šคํฌํ†ฑ์—์„œ๋Š” ์ œํ•œ ์—†์Œ -->
<p class="line-clamp-3 lg:line-clamp-none">
  ๋ชจ๋ฐ”์ผ์—์„œ๋Š” 3์ค„๋งŒ ๋ณด์ด์ง€๋งŒ, lg ์ด์ƒ์—์„œ๋Š” ์ „์ฒด ํ…์ŠคํŠธ๊ฐ€ ๋ณด์—ฌ.
</p>
 
<!-- ์ƒํƒœ ๊ธฐ๋ฐ˜ ํ† ๊ธ€ (React) -->
export function ExpandableText({ text }: { text: string }) {
  const [expanded, setExpanded] = useState(false);
  return (
    <div>
      <p className={expanded ? 'line-clamp-none' : 'line-clamp-3'}>
        {text}
      </p>
      <button onClick={() => setExpanded(!expanded)} className="text-indigo-600 text-sm mt-1">
        {expanded ? '์ ‘๊ธฐ' : '๋” ๋ณด๊ธฐ'}
      </button>
    </div>
  );
}

line-clamp ๋ฅผ ์ปค์Šคํ…€ ๊ฐ’์œผ๋กœ

<!-- ๊ธฐ๋ณธ ์ œ๊ณต: line-clamp-1 ~ line-clamp-6 -->
<p class="line-clamp-1">...</p>  <!-- 1์ค„ -->
<p class="line-clamp-2">...</p>  <!-- 2์ค„ -->
<p class="line-clamp-6">...</p>  <!-- 6์ค„ -->
 
<!-- ์ปค์Šคํ…€ ๊ฐ’ -->
<p class="line-clamp-[10]">10์ค„๊นŒ์ง€</p>
<p class="line-clamp-[--my-line-count]">CSS ๋ณ€์ˆ˜๋กœ ์ œ์–ด</p>

โš–๏ธ 2๋‹จ๊ณ„: text-balance & text-pretty โ€” ํ…์ŠคํŠธ ๊ท ํ˜•์˜ ๋ฏธํ•™

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • ์ œ๋ชฉ๊ณผ ๋ณธ๋ฌธ์— ๊ฐ๊ฐ ์–ด๋–ค text-wrap ์œ ํ‹ธ๋ฆฌํ‹ฐ๊ฐ€ ์ ํ•ฉํ•œ์ง€ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž๋™์œผ๋กœ ์ค„๋ฐ”๊ฟˆ์„ ์ตœ์ ํ™”ํ•˜๋Š” "๊ณ ์•„ ๋‹จ์–ด(orphan)" ๋ฌธ์ œ๋ฅผ ์ดํ•ดํ•œ๋‹ค.

์ด ์„น์…˜์€ ๊ฐœ๋ฐœ์ž๋“ค์ด ๊ฐ€์žฅ ๋ชจ๋ฅด๋Š” ์ˆจ๊ฒจ์ง„ ๋ณด์„ ๊ฐ™์€ ๊ธฐ๋Šฅ์ด์•ผ.

๊ณ ์•„ ๋‹จ์–ด(Orphan) ๋ฌธ์ œ

์ œ๋ชฉ์ด ์ด๋ ‡๊ฒŒ ํ‘œ์‹œ๋  ๋•Œ:
"๊ฐœ๋ฐœ์ž ์Šคํ„ฐ๋”” ๋งค์นญ ํ”Œ๋žซํผ โ€” ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๋Š”
์ปค๋ฎค๋‹ˆํ‹ฐ"

๋งˆ์ง€๋ง‰์— "์ปค๋ฎค๋‹ˆํ‹ฐ" ๋‹จ์–ด ํ•˜๋‚˜๋งŒ ๋ฉ๊ทธ๋Ÿฌ๋‹ˆ 2๋ฒˆ์งธ ์ค„์— ๋–จ์–ด์ ธ ์žˆ์–ด.
์ด๊ฒŒ ๋ฐ”๋กœ "orphan(๊ณ ์•„ ๋‹จ์–ด)" ๋ฌธ์ œ์•ผ. ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ์—์„œ ๊ฐ€์žฅ ๋ˆˆ์— ๊ฑฐ์Šฌ๋ฆฌ๋Š” ํ˜„์ƒ ์ค‘ ํ•˜๋‚˜.

text-balance โ€” ์ค„๋งˆ๋‹ค ๋น„์Šทํ•œ ๊ธธ์ด๋กœ ๊ท ํ˜• ๋งž์ถ”๊ธฐ

<!-- โŒ ๊ธฐ๋ณธ ๋™์ž‘: ์ฒซ ์ค„์„ ์ตœ๋Œ€ํ•œ ์ฑ„์šฐ๊ณ  ๋‚˜๋จธ์ง€๊ฐ€ 2๋ฒˆ์งธ ์ค„๋กœ -->
<h1 class="text-4xl font-bold max-w-sm">
  ๊ฐœ๋ฐœ์ž ์Šคํ„ฐ๋”” ๋งค์นญ ํ”Œ๋žซํผ โ€” ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๋Š” ์ปค๋ฎค๋‹ˆํ‹ฐ
  <!-- ๋ Œ๋”๋ง:
    "๊ฐœ๋ฐœ์ž ์Šคํ„ฐ๋”” ๋งค์นญ ํ”Œ๋žซํผ โ€” ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๋Š”"  โ† ๊ฝ‰ ์ฐธ
    "์ปค๋ฎค๋‹ˆํ‹ฐ"  โ† ๊ณ ์•„ ๋‹จ์–ด! -->
</h1>
 
<!-- โœ… text-balance: ๊ฐ ์ค„์˜ ๊ธธ์ด๋ฅผ ์ตœ๋Œ€ํ•œ ๊ท ๋“ฑํ•˜๊ฒŒ -->
<h1 class="text-4xl font-bold max-w-sm text-balance">
  ๊ฐœ๋ฐœ์ž ์Šคํ„ฐ๋”” ๋งค์นญ ํ”Œ๋žซํผ โ€” ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๋Š” ์ปค๋ฎค๋‹ˆํ‹ฐ
  <!-- ๋ Œ๋”๋ง:
    "๊ฐœ๋ฐœ์ž ์Šคํ„ฐ๋”” ๋งค์นญ"  โ† ๊ท ๋“ฑ
    "ํ”Œ๋žซํผ โ€” ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๋Š” ์ปค๋ฎค๋‹ˆํ‹ฐ"  โ† ๊ท ๋“ฑ -->
</h1>

text-balance ๋ฅผ ์“ธ ๋•Œ ์ฃผ์˜:

  • ์งง์€ ์ œ๋ชฉ(ํ—ค๋”ฉ)์—์„œ ๋น›์„ ๋ฐœํ•จ: 2~5์ค„ ์ •๋„์˜ h1, h2 ์ œ๋ชฉ์— ์ด์ƒ์ 
  • ๊ธด ๋ณธ๋ฌธ์—๋Š” ์‚ฌ์šฉํ•˜์ง€ ๋ง ๊ฒƒ: ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๋ชจ๋“  ์ค„์˜ ๊ท ํ˜•์„ ๊ณ„์‚ฐํ•˜๋Š” ๋น„์šฉ์ด ๋ฐœ์ƒ
<!-- ์˜ฌ๋ฐ”๋ฅธ ์‚ฌ์šฉ ํŒจํ„ด -->
<h1 class="text-balance">์งง์€ ํžˆ์–ด๋กœ ์ œ๋ชฉ</h1>  <!-- โœ… -->
<h2 class="text-balance">์นด๋“œ ์„น์…˜ ์ œ๋ชฉ</h2>    <!-- โœ… -->
<p class="text-balance">๊ธด ๋ณธ๋ฌธ ํ…์ŠคํŠธ...</p>   <!-- โŒ ์„ฑ๋Šฅ ์ด์Šˆ -->

text-pretty โ€” ๋งˆ์ง€๋ง‰ ์ค„ ๊ณ ์•„ ๋‹จ์–ด๋งŒ ๋ฐฉ์ง€

text-balance๊ฐ€ ๋ชจ๋“  ์ค„์„ ๊ท ๋“ฑํ•˜๊ฒŒ ๋งž์ถ”๋Š” ๋ฐ˜๋ฉด, text-pretty๋Š” ๋” ๊ฐ€๋ฒผ์šด ์ตœ์ ํ™”์•ผ. ๋งˆ์ง€๋ง‰ ์ค„์— ๋‹จ์–ด๊ฐ€ ํ•˜๋‚˜๋งŒ ๋‚จ๋Š” "orphan" ์ƒํ™ฉ๋งŒ ๋ฐฉ์ง€ํ•ด.

<!-- text-pretty: ๋งˆ์ง€๋ง‰ ์ค„์—๋งŒ orphan ๋ฐฉ์ง€ ์ตœ์ ํ™” -->
<!-- ๋ณธ๋ฌธ ํ…์ŠคํŠธ์ฒ˜๋Ÿผ ๊ธธ์–ด๋„ ์„ฑ๋Šฅ ์ด์Šˆ ์—†์Œ -->
<p class="text-pretty">
  ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ๋Š” ๊ฐœ๋ฐœ์ž๋“ค์ด ํ•จ๊ป˜ ๋ชจ์—ฌ ์„ฑ์žฅํ•˜๋Š” ๊ณต๊ฐ„์ด์—์š”.
  React, TypeScript, ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋“ฑ ๋‹ค์–‘ํ•œ ์ฃผ์ œ์˜ ์Šคํ„ฐ๋””๋ฅผ ์ž์œ ๋กญ๊ฒŒ
  ๋งŒ๋“ค๊ณ  ์ฐธ์—ฌํ•  ์ˆ˜ ์žˆ์–ด์š”. ์ง€๊ธˆ ๋ฐ”๋กœ ์‹œ์ž‘ํ•ด๋ณด์„ธ์š”.
  <!-- ๋งˆ์ง€๋ง‰ ์ค„์— ๋‹จ์–ด 1๊ฐœ๋งŒ ๋‚จ๋Š” ์ƒํ™ฉ ์ž๋™ ๋ฐฉ์ง€ -->
</p>

text-balance vs text-pretty ์„ ํƒ ๊ฐ€์ด๋“œ:

์ƒํ™ฉ์ถ”์ฒœ
h1, h2 ๊ฐ™์€ ์งง์€ ์ œ๋ชฉtext-balance
์นด๋“œ ๋ถ€์ œ๋ชฉ (2~3์ค„)text-balance
๊ธด ๋ณธ๋ฌธ, ์„ค๋ช… ํ…์ŠคํŠธtext-pretty
๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ bodytext-pretty

๐Ÿ”ข 3๋‹จ๊ณ„: font-variant-numeric โ€” ์ˆซ์ž๋ฅผ ๋””์ž์ธํ•˜๋‹ค

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • ๋ฐ์ดํ„ฐ ํ…Œ์ด๋ธ”์—์„œ ์ˆซ์ž ์ •๋ ฌ์ด ์•ˆ ๋งž๋Š” ์ด์œ ์™€ ํ•ด๊ฒฐ์ฑ…์„ ์ดํ•ดํ•œ๋‹ค.
  • tabular-nums, slashed-zero, ordinal ๋“ฑ์˜ ์‹ค๋ฌด ์ ์šฉ ๋ฐฉ๋ฒ•์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

๋ฌธ์ œ: ์ˆซ์ž ํญ์ด ๋‹ฌ๋ผ์„œ ์„ธ๋กœ ์ •๋ ฌ์ด ์•ˆ ๋งž์Œ

์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ ์Šคํ„ฐ๋”” ์ฐธ์—ฌ ํ˜„ํ™ฉ:
  ์ฐธ์—ฌ์ž ์ˆ˜: 1,234๋ช…    โ† '1'์ด ์ข์•„์„œ ์ •๋ ฌ์ด ํ‹€์–ด์ง
  ๊ฒŒ์‹œ๊ธ€ ์ˆ˜: 89,012๊ฐœ   โ† '8','9'๊ฐ€ ๋„“์–ด์„œ ์•ž์œผ๋กœ ๋‚˜์˜ด
  ํ™œ์„ฑ ์Šคํ„ฐ๋””: 456๊ฐœ    โ† ...

์†Œ์ˆ˜์ ๋„ ์•ˆ ๋งž์Œ:
  ํ‰์ : 4.9  โ† ์†Œ์ˆ˜์  ์œ„์น˜๊ฐ€ ์ œ๊ฐ๊ฐ
  ํ‰์ : 10.0 โ† '10'์ด '4'๋ณด๋‹ค ๋„“์–ด์„œ ์†Œ์ˆ˜์  ์œ„์น˜๊ฐ€ ๋‹ค๋ฆ„

tabular-nums โ€” ๋ชจ๋“  ์ˆซ์ž ํญ์„ ๋™์ผํ•˜๊ฒŒ

<!-- โŒ ๊ธฐ๋ณธ ์ˆซ์ž ํฐํŠธ: ๊ฐ ์ˆซ์ž์˜ ํญ์ด ๋‹ค๋ฆ„ (proportional) -->
<!-- 1์€ ์ข๊ณ , 0์€ ๋„“์–ด์„œ ์†Œ์ˆ˜์  ์ •๋ ฌ์ด ์•ˆ ๋จ -->
<table>
  <tr><td>1,234</td></tr>
  <tr><td>89,012</td></tr>
</table>
 
<!-- โœ… tabular-nums: ๋ชจ๋“  ์ˆซ์ž(0~9)์˜ ํญ์ด ๋™์ผ -->
<table class="tabular-nums">
  <tr><td class="text-right">1,234</td></tr>   <!-- '1' ํญ = '9' ํญ โ†’ ์ •๋ ฌ ๋งž์Œ! -->
  <tr><td class="text-right">89,012</td></tr>
</table>

slashed-zero โ€” 0๊ณผ O ํ˜ผ๋™ ๋ฐฉ์ง€

<!-- ๊ฐœ๋ฐœ์ž ID๋‚˜ ์ฝ”๋“œ ํ‘œ์‹œ์— ์œ ์šฉ -->
<code class="slashed-zero font-mono text-sm bg-gray-100 px-1 rounded">
  O0oO0  <!-- 0์€ ๊ฐ€์šด๋ฐ ์‚ฌ์„ , O๋Š” ์—†์–ด์„œ ๊ตฌ๋ถ„ ๋ช…ํ™• -->
</code>

ordinal โ€” ์„œ์ˆ˜ ํ‘œํ˜„ ์ตœ์ ํ™”

<!-- ์Šคํ„ฐ๋”” ์ˆœ์œ„ ํ‘œ์‹œ -->
<span class="ordinal font-semibold text-indigo-600">1st</span>
<span class="ordinal font-semibold text-indigo-600">2nd</span>
<span class="ordinal font-semibold text-indigo-600">3rd</span>
<!-- ordinal: ํฐํŠธ์—์„œ ์ œ๊ณตํ•˜๋Š” ํŠน์ˆ˜ ์„œ์ˆ˜ ๊ธ€๋ฆฌํ”„ ์‚ฌ์šฉ -->

๋ณตํ•ฉ ์กฐํ•ฉ

<!-- ๊ธˆ์•ก ํ‘œ์‹œ ํ…Œ์ด๋ธ” - slashed-zero + tabular-nums ์กฐํ•ฉ -->
<dl class="space-y-2">
  <div class="flex justify-between">
    <dt>์Šคํ„ฐ๋”” ์ฐธ์—ฌ๋น„</dt>
    <dd class="tabular-nums slashed-zero font-mono">โ‚ฉ50,000</dd>
  </div>
  <div class="flex justify-between">
    <dt>ํ”Œ๋žซํผ ์ˆ˜์ˆ˜๋ฃŒ</dt>
    <dd class="tabular-nums slashed-zero font-mono">โ‚ฉ5,000</dd>
  </div>
  <div class="flex justify-between font-bold border-t pt-2">
    <dt>ํ•ฉ๊ณ„</dt>
    <dd class="tabular-nums slashed-zero font-mono">โ‚ฉ55,000</dd>
  </div>
</dl>

font-variant-numeric ์ „์ฒด ๋ชฉ๋ก:

์œ ํ‹ธ๋ฆฌํ‹ฐํšจ๊ณผ์ฃผ์š” ์šฉ๋„
tabular-nums๋ชจ๋“  ์ˆซ์ž ํญ ๋™์ผ๊ธˆ์•ก/ํ†ต๊ณ„ ํ…Œ์ด๋ธ” ์ •๋ ฌ
proportional-nums์ˆซ์ž ํญ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ์ผ๋ฐ˜ ๋ณธ๋ฌธ ์ˆซ์ž
lining-nums์ˆซ์ž๊ฐ€ ๋ชจ๋‘ baseline ์œ„์—์ œ๋ชฉ์˜ ์ˆซ์ž
oldstyle-nums์ผ๋ถ€ ์ˆซ์ž์— descender๊ณ ์ „์  ํŽธ์ง‘ ๋А๋‚Œ
slashed-zero0์— ์‚ฌ์„ ์ฝ”๋“œ, ID ํ‘œ์‹œ
ordinal์„œ์ˆ˜ ํŠน์ˆ˜ ๊ธ€๋ฆฌํ”„1st, 2nd ํ‘œ๊ธฐ
diagonal-fractions1/2 โ†’ ํŠน์ˆ˜ ๋ถ„์ˆ˜ ๊ธ€๋ฆฌํ”„๋ ˆ์‹œํ”ผ, ์ˆ˜ํ•™ ํ‘œํ˜„

๐Ÿ”ฌ 4๋‹จ๊ณ„: font-feature-settings โ€” OpenType ๊ธฐ๋Šฅ ์ž ๊ธˆ ํ•ด์ œ

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • OpenType feature๊ฐ€ ๋ฌด์—‡์ธ์ง€ ์ดํ•ดํ•˜๊ณ , Tailwind๋กœ ํ™œ์„ฑํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
  • font-variant-numeric ์ด font-feature-settings ์˜ ๊ณ ์ˆ˜์ค€ ์ถ”์ƒํ™”์ž„์„ ์ดํ•ดํ•œ๋‹ค.

OpenType Feature๋ž€?

ํ˜„๋Œ€ ํฐํŠธ(OpenType)์—๋Š” ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ์ˆจ๊ฒจ์ง„ ๋‹ค์–‘ํ•œ ๊ธ€์ž ๋ณ€ํ˜•์ด ๋‚ด์žฅ๋˜์–ด ์žˆ์–ด. ์ด๋ฅผ font-feature-settings๋กœ ์ž ๊ธˆ ํ•ด์ œํ•  ์ˆ˜ ์žˆ์–ด.

<!-- ๊ธฐ๋ณธ font-feature-settings ํ™œ์„ฑํ™” -->
<p class="font-features-['smcp']">Small Caps: ABC โ†’ แด€ส™แด„</p>
<!-- smcp: Small Caps - ์†Œ๋ฌธ์ž๋ฅผ ์†Œํ˜• ๋Œ€๋ฌธ์ž๋กœ ๋ณ€ํ™˜ -->
 
<p class="font-features-['onum']">Oldstyle Nums: 1234 โ†’ ํด๋ž˜์‹ ์Šคํƒ€์ผ ์ˆซ์ž</p>
<!-- onum: Oldstyle numerals - ํด๋ž˜์‹ ํŽธ์ง‘ ์Šคํƒ€์ผ ์ˆซ์ž -->
 
<p class="font-features-['liga']">Standard Ligatures: fi, fl โ†’ ํ•ฉ์ž</p>
<!-- liga: Ligatures - fi, fl ๊ฐ™์€ ๊ธ€์ž๊ฐ€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์—ฐ๊ฒฐ๋จ -->
 
<!-- ์—ฌ๋Ÿฌ feature ๋™์‹œ ํ™œ์„ฑํ™” -->
<p class="font-features-['smcp','tnum','onum']">
  ๋ณตํ•ฉ OpenType ๊ธฐ๋Šฅ ํ™œ์„ฑํ™”
</p>

์ž์ฃผ ์“ฐ์ด๋Š” OpenType Feature ํƒœ๊ทธ:

ํƒœ๊ทธ์ด๋ฆ„ํšจ๊ณผ
smcpSmall Caps์†Œ๋ฌธ์ž๋ฅผ ์†Œํ˜• ๋Œ€๋ฌธ์ž๋กœ
onumOldstyle Numsํด๋ž˜์‹ ์Šคํƒ€์ผ ์ˆซ์ž
tnumTabular Nums๋“ฑํญ ์ˆซ์ž (tabular-nums์™€ ๋™์ผ)
ligaLigaturesfi, fl ๋“ฑ ํ•ฉ์ž
caltContextual Alternates๋ฌธ๋งฅ๋ณ„ ๊ธ€์ž ๋ณ€ํ˜•
caseCase-Sensitive Forms๋Œ€๋ฌธ์ž ์ „์šฉ ๊ตฌ๋‘์  ์ตœ์ ํ™”

์ฐธ๊ณ : tabular-nums, slashed-zero ๊ฐ™์€ font-variant-numeric ์œ ํ‹ธ๋ฆฌํ‹ฐ๋“ค์€ ๋‚ด๋ถ€์ ์œผ๋กœ font-feature-settings๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ณ ์ˆ˜์ค€ ์ถ”์ƒํ™”์•ผ. ๊ฐ€๋Šฅํ•˜๋ฉด ๋†’์€ ์ˆ˜์ค€์˜ ์œ ํ‹ธ๋ฆฌํ‹ฐ๋ฅผ ๋จผ์ € ์“ฐ๊ณ , ์—†์„ ๋•Œ๋งŒ font-features-[...]๋ฅผ ์ง์ ‘ ์“ฐ๋Š” ๊ฒƒ์ด ์ข‹์•„.


๐Ÿ“– 5๋‹จ๊ณ„: @tailwindcss/typography โ€” Prose ํ”Œ๋Ÿฌ๊ทธ์ธ ๋งˆ์Šคํ„ฐ

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • @tailwindcss/typography ํ”Œ๋Ÿฌ๊ทธ์ธ์˜ ์—ญํ• ๊ณผ ์„ค์น˜ ๋ฐฉ๋ฒ•์„ ์ดํ•ดํ•œ๋‹ค.
  • prose ํด๋ž˜์Šค์˜ ์ˆ˜์‹์–ด(modifier)๋กœ ํฌ๊ธฐ, ์ƒ‰์ƒ, ๋‹คํฌ ๋ชจ๋“œ๋ฅผ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ์žˆ๋‹ค.

์™œ prose ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ํ•„์š”ํ•œ๊ฐ€?

์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ์— ๋งˆํฌ๋‹ค์šด์œผ๋กœ ์ž‘์„ฑํ•˜๋Š” ์Šคํ„ฐ๋”” ๊ณต์ง€์‚ฌํ•ญ์ด๋‚˜ ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•œ๋‹ค๊ณ  ํ•ด๋ณด์ž.

// Markdown์„ HTML๋กœ ๋ณ€ํ™˜ํ•œ ๊ฒฐ๊ณผ๋ฅผ dangerouslySetInnerHTML๋กœ ์‚ฝ์ž…
export default function PostContent({ htmlContent }: { htmlContent: string }) {
  return (
    // โŒ ์Šคํƒ€์ผ ์—†์Œ: h1, h2, p, ul, code ๋“ฑ์ด ๋ชจ๋‘ ๊ธฐ๋ณธ ๋ธŒ๋ผ์šฐ์ € ์Šคํƒ€์ผ
    <div dangerouslySetInnerHTML={{ __html: htmlContent }} />
    // ๊ฒฐ๊ณผ: ๋ณผํ’ˆ์—†๋Š” ๋‚  HTML. ์ œ๋ชฉ๋„ ๋ณธ๋ฌธ๋„ ๋‹ค ๊ฐ™์€ ํฌ๊ธฐ์ฒ˜๋Ÿผ ๋ณด์ž„
  );
}

Tailwind๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ชจ๋“  HTML ์š”์†Œ๋ฅผ ๋ฆฌ์…‹(reset) ํ•ด์„œ h1๊ณผ p๊ฐ€ ๊ฐ™์€ ํฌ๊ธฐ๋กœ ๋ณด์—ฌ. ์˜๋„์ ์ธ ๋””์ž์ธ์ด์•ผ โ€” ๊ฐœ๋ฐœ์ž๊ฐ€ ์ง์ ‘ ์Šคํƒ€์ผ์„ ํ†ต์ œํ•˜๋„๋ก. ํ•˜์ง€๋งŒ ๋งˆํฌ๋‹ค์šด ๋ Œ๋”๋ง ์˜์—ญ์—์„œ๋Š” ์ด๊ฒŒ ๋ฌธ์ œ์•ผ. h1์€ ํฌ๊ฒŒ, h2๋Š” ์ค‘๊ฐ„, code๋Š” ๋ชจ๋…ธ์ŠคํŽ˜์ด์Šค๋กœ ๋ณด์—ฌ์•ผ ํ•˜๋Š”๋ฐ ์•„๋ฌด๊ฒƒ๋„ ์—†์œผ๋‹ˆ๊นŒ.

์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒŒ @tailwindcss/typography ํ”Œ๋Ÿฌ๊ทธ์ธ (prose ํด๋ž˜์Šค)์ด์•ผ.

์„ค์น˜ ๋ฐ ์„ค์ •

# ์„ค์น˜
npm install @tailwindcss/typography
/* tailwind.css - Tailwind v4 ๋ฐฉ์‹ */
@import "tailwindcss";
@plugin "@tailwindcss/typography";
// tailwind.config.js - Tailwind v3 ๋ฐฉ์‹
module.exports = {
  plugins: [
    require('@tailwindcss/typography'),
  ],
}

prose ๊ธฐ๋ณธ ์‚ฌ์šฉ

// components/PostContent.tsx
// ๐Ÿฆ ์˜ํ˜ธ: "๋งˆํฌ๋‹ค์šด ๋ Œ๋”๋ง ์˜์—ญ์— prose ํด๋ž˜์Šค ํ•˜๋‚˜๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด ๋์ด์•ผ"
 
export default function PostContent({ htmlContent }: { htmlContent: string }) {
  return (
    // prose: ๋ชจ๋“  ํ•˜์œ„ HTML ์š”์†Œ์— ์ฝ๊ธฐ ์ข‹์€ ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ ์Šคํƒ€์ผ ์ž๋™ ์ ์šฉ
    <div
      className="prose"
      dangerouslySetInnerHTML={{ __html: htmlContent }}
    />
    // ๊ฒฐ๊ณผ:
    // h1 โ†’ ํฐ ์ œ๋ชฉ (2.25em, ๊ตต๊ฒŒ, ์—ฌ๋ฐฑ ํฌํ•จ)
    // h2 โ†’ ์ค‘๊ฐ„ ์ œ๋ชฉ (1.5em, ๊ตต๊ฒŒ)
    // p โ†’ ์ ์ ˆํ•œ line-height์™€ ์—ฌ๋ฐฑ
    // ul/ol โ†’ ๋“ค์—ฌ์“ฐ๊ธฐ์™€ ๋งˆ์ปค
    // code โ†’ ๋ชจ๋…ธ์ŠคํŽ˜์ด์Šค, ๋ฐฐ๊ฒฝ์ƒ‰
    // blockquote โ†’ ์™ผ์ชฝ ๋ณด๋”, ์ดํƒค๋ฆญ
    // img โ†’ ์ž๋™ max-width
    // a โ†’ ์ƒ‰์ƒ๊ณผ ๋ฐ‘์ค„
  );
}

prose ์ˆ˜์‹์–ด (ํฌ๊ธฐ ๋ณ€ํ˜•)

<!-- ํฌ๊ธฐ ์ˆ˜์‹์–ด: prose-sm, prose-base, prose-lg, prose-xl, prose-2xl -->
<div class="prose prose-sm">์†Œํ˜• ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ (๋ธ”๋กœ๊ทธ ๋ฉ”๋ชจ, ๋ถ€์—ฐ ์„ค๋ช…)</div>
<div class="prose">๊ธฐ๋ณธ (prose-base)</div>
<div class="prose prose-lg">๋Œ€ํ˜• ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ (์ฃผ์š” ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŠธ)</div>
<div class="prose prose-xl">ํŠน๋Œ€ํ˜• (๋‰ด์Šค๋ ˆํ„ฐ, ๋งค๊ฑฐ์ง„ ์Šคํƒ€์ผ)</div>
<div class="prose prose-2xl">์ตœ๋Œ€ํ˜• (ํžˆ์–ด๋กœ ํ…์ŠคํŠธ ์˜์—ญ)</div>

๋‹คํฌ ๋ชจ๋“œ ๋Œ€์‘

<!-- prose-invert: ์–ด๋‘์šด ๋ฐฐ๊ฒฝ์— ๋งž๋Š” ๋ฐ์€ ์ƒ‰์ƒ์œผ๋กœ ์ „ํ™˜ -->
<div class="prose dark:prose-invert">
  ๋‹คํฌ ๋ชจ๋“œ์—์„œ ์ž๋™์œผ๋กœ ํ…์ŠคํŠธ์™€ ์š”์†Œ ์ƒ‰์ƒ์ด ๋ฐ์€ ํ†ค์œผ๋กœ ๋ฐ”๋€œ
</div>

prose ์ƒ‰์ƒ ์ˆ˜์‹์–ด

<!-- prose-{color}: ๋งํฌ, ๊ฐ•์กฐ ๋“ฑ์˜ ํฌ์ธํŠธ ์ƒ‰์ƒ ๋ณ€๊ฒฝ -->
<div class="prose prose-indigo">์ธ๋””๊ณ  ํฌ์ธํŠธ ์ƒ‰์ƒ</div>
<div class="prose prose-violet">๋ณด๋ผ ํฌ์ธํŠธ ์ƒ‰์ƒ</div>
<div class="prose prose-emerald">์—๋ฉ”๋ž„๋“œ ํฌ์ธํŠธ ์ƒ‰์ƒ</div>

ํŠน์ • ์š”์†Œ๋งŒ ์ˆ˜์ •ํ•˜๊ธฐ

<!-- prose-h2:text-indigo-600: h2 ์ œ๋ชฉ ์ƒ‰์ƒ๋งŒ ์ธ๋””๊ณ ๋กœ -->
<!-- prose-a:no-underline: ๋งํฌ ๋ฐ‘์ค„ ์ œ๊ฑฐ -->
<!-- prose-code:text-pink-500: ์ธ๋ผ์ธ ์ฝ”๋“œ ์ƒ‰์ƒ ๋ณ€๊ฒฝ -->
<div class="prose
  prose-h2:text-indigo-600
  prose-a:no-underline
  prose-a:font-medium
  prose-code:text-pink-500
  prose-code:bg-pink-50
  prose-code:px-1.5
  prose-code:rounded
">
  <!-- ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ปค์Šคํ„ฐ๋งˆ์ด์ฆˆ๋œ prose ์˜์—ญ -->
</div>

์‹ค์ „: ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ ์Šคํ„ฐ๋”” ๊ณต์ง€์‚ฌํ•ญ ์ƒ์„ธ ํŽ˜์ด์ง€

// app/studies/[id]/notice/[noticeId]/page.tsx
// ๐Ÿฆ ์˜ํ˜ธ: "๊ณต์ง€์‚ฌํ•ญ ๋งˆํฌ๋‹ค์šด์„ prose๋กœ ๋ Œ๋”๋งํ•ด. ๋‹คํฌ ๋ชจ๋“œ๊นŒ์ง€ ํ•œ ๋ฐฉ์—."
 
import { marked } from 'marked';  // ๋˜๋Š” next-mdx-remote ๋“ฑ
 
async function getNotice(noticeId: string) {
  // ... DB์—์„œ ๊ณต์ง€์‚ฌํ•ญ ๊ฐ€์ ธ์˜ค๊ธฐ
  return { title: "React ์Šคํ„ฐ๋”” 1ํšŒ์ฐจ ๊ณต์ง€", content: "## ์‹œ๊ฐ„ ์•ˆ๋‚ด\n..." };
}
 
export default async function NoticePage({ params }) {
  const notice = await getNotice(params.noticeId);
  const htmlContent = marked(notice.content);  // Markdown โ†’ HTML ๋ณ€ํ™˜
 
  return (
    <article className="max-w-3xl mx-auto px-4 py-8">
      <h1 className="text-3xl font-bold text-gray-900 mb-2">{notice.title}</h1>
      <time className="text-sm text-gray-500 mb-8 block">2025๋…„ 1์›” 15์ผ</time>
 
      {/* prose๋กœ ๋งˆํฌ๋‹ค์šด ๋ Œ๋”๋ง ์˜์—ญ ์Šคํƒ€์ผ๋ง */}
      <div
        className="
          prose prose-lg           /* ๊ธฐ๋ณธ + ํฐ ํฌ๊ธฐ */
          dark:prose-invert        /* ๋‹คํฌ ๋ชจ๋“œ */
          prose-indigo             /* ๋งํฌ, ๊ฐ•์กฐ์ƒ‰ ์ธ๋””๊ณ  */
          prose-headings:font-bold  /* ๋ชจ๋“  ํ—ค๋”ฉ ๊ตต๊ฒŒ */
          prose-code:text-sm       /* ์ฝ”๋“œ ํฐํŠธ ์ž‘๊ฒŒ */
          max-w-none               /* prose์˜ max-width ์ œํ•œ ํ•ด์ œ */
        "
        dangerouslySetInnerHTML={{ __html: htmlContent }}
      />
    </article>
  );
}

๐Ÿ“ 6๋‹จ๊ณ„: Fluid Typography โ€” ๋ทฐํฌํŠธ์— ๋ฐ˜์‘ํ•˜๋Š” ๊ธ€์ž ํฌ๊ธฐ

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • clamp() CSS ํ•จ์ˆ˜์˜ ์ž‘๋™ ์›๋ฆฌ๋ฅผ ์ดํ•ดํ•œ๋‹ค.
  • Tailwind์—์„œ ์ž„์˜๊ฐ’ ๋ฌธ๋ฒ•์œผ๋กœ fluid typography๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

๋ฐ˜์‘ํ˜• ํฐํŠธ ํฌ๊ธฐ์˜ ๋ฌธ์ œ์ 

<!-- โŒ ๊ธฐ์กด ๋ฐ˜์‘ํ˜• ๋ฐฉ์‹: ๋ธŒ๋ ˆ์ดํฌํฌ์ธํŠธ์—์„œ ๊ฐ‘์ž๊ธฐ ํฌ๊ธฐ๊ฐ€ ๋ฐ”๋€œ -->
<h1 class="text-2xl md:text-4xl lg:text-6xl">
  ๋ชจ๋ฐ”์ผ: 24px โ†’ 768px์—์„œ ๊ฐ‘์ž๊ธฐ 36px โ†’ 1024px์—์„œ ๊ฐ‘์ž๊ธฐ 60px
  (์ค‘๊ฐ„ ํฌ๊ธฐ ๊ธฐ๊ธฐ์—์„œ ์–ด์ƒ‰ํ•œ ํฌ๊ธฐ)
</h1>

CSS clamp() ํ•จ์ˆ˜

clamp(์ตœ์†Œ, ์„ ํ˜ธ, ์ตœ๋Œ€) ํ•จ์ˆ˜๋Š” ์„ธ ๊ฐ’ ์ค‘ ํ˜„์žฌ ์ƒํ™ฉ์— ๋งž๋Š” ๊ฐ’์„ ์ž๋™์œผ๋กœ ์„ ํƒํ•ด:

  • ๊ณ„์‚ฐ๊ฐ’์ด ์ตœ์†Œ๋ณด๋‹ค ์ž‘์œผ๋ฉด โ†’ ์ตœ์†Œ ์‚ฌ์šฉ
  • ๊ณ„์‚ฐ๊ฐ’์ด ์ตœ์†Œ~์ตœ๋Œ€ ์‚ฌ์ด๋ฉด โ†’ ๊ณ„์‚ฐ๊ฐ’ ์‚ฌ์šฉ (๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋ณ€ํ™”)
  • ๊ณ„์‚ฐ๊ฐ’์ด ์ตœ๋Œ€๋ณด๋‹ค ํฌ๋ฉด โ†’ ์ตœ๋Œ€ ์‚ฌ์šฉ
/* ๊ฐœ๋…: font-size๊ฐ€ ๋ทฐํฌํŠธ์— ๋น„๋ก€ํ•ด ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ๋ณ€ํ•จ */
font-size: clamp(1.5rem, 4vw, 4rem);
/*
  ๋ทฐํฌํŠธ 375px (๋ชจ๋ฐ”์ผ):
    4vw = 15px < 24px(1.5rem) โ†’ 24px ์‚ฌ์šฉ (์ตœ์†Œ)
  ๋ทฐํฌํŠธ 600px:
    4vw = 24px โ†’ 24px ์‚ฌ์šฉ
  ๋ทฐํฌํŠธ 1200px:
    4vw = 48px < 64px(4rem) โ†’ 48px ์‚ฌ์šฉ
  ๋ทฐํฌํŠธ 2000px:
    4vw = 80px > 64px(4rem) โ†’ 64px ์‚ฌ์šฉ (์ตœ๋Œ€)
*/

Tailwind์—์„œ fluid typography ๊ตฌํ˜„

<!-- Tailwind ์ž„์˜๊ฐ’ ๋ฌธ๋ฒ•์œผ๋กœ clamp() ์‚ฌ์šฉ -->
<h1 class="text-[clamp(1.5rem,4vw,4rem)]">
  ๋ทฐํฌํŠธ์— ๋น„๋ก€ํ•ด ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์ปค์ง€๋Š” ์ œ๋ชฉ
</h1>
 
<!-- ๋” ์ •๊ตํ•œ ๊ณ„์‚ฐ: calc()์™€ vw ์กฐํ•ฉ -->
<h2 class="text-[clamp(1.25rem,_2.5vw_+_1rem,_3rem)]">
  <!-- _๋Š” ๊ณต๋ฐฑ ๋Œ€์‹  ์‚ฌ์šฉ (Tailwind ์ž„์˜๊ฐ’์—์„œ ๊ณต๋ฐฑ ํ—ˆ์šฉ ์•ˆ ํ•จ) -->
  ์ตœ์†Œ 1.25rem, ์ตœ๋Œ€ 3rem, ์ค‘๊ฐ„์€ ์„ ํ˜• ๋ณด๊ฐ„
</h2>

tailwind.css์— fluid ํฌ๊ธฐ ๋“ฑ๋ก (์žฌ์‚ฌ์šฉ)

/* tailwind.css - ํ”„๋กœ์ ํŠธ ์ „์ฒด์—์„œ ์“ธ fluid ํฌ๊ธฐ ๋“ฑ๋ก */
@theme {
  /* Fluid Typography Tokens */
  --text-fluid-sm: clamp(0.875rem, 0.8rem + 0.375vw, 1rem);
  --text-fluid-base: clamp(1rem, 0.9rem + 0.5vw, 1.25rem);
  --text-fluid-lg: clamp(1.125rem, 1rem + 0.625vw, 1.5rem);
  --text-fluid-xl: clamp(1.25rem, 1.1rem + 0.75vw, 1.875rem);
  --text-fluid-2xl: clamp(1.5rem, 1.2rem + 1.5vw, 2.5rem);
  --text-fluid-3xl: clamp(1.875rem, 1.5rem + 1.875vw, 3.5rem);
  --text-fluid-4xl: clamp(2.25rem, 1.75rem + 2.5vw, 4.5rem);
}
<!-- ๋“ฑ๋ก๋œ fluid ํฌ๊ธฐ ์‚ฌ์šฉ -->
<h1 class="text-(length:--text-fluid-4xl) font-black">
  ์–ด๋–ค ๊ธฐ๊ธฐ์—์„œ๋„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ํฌ๊ธฐ๊ฐ€ ์กฐ์ •๋˜๋Š” ํžˆ์–ด๋กœ ํƒ€์ดํ‹€
</h1>
<p class="text-(length:--text-fluid-base)">
  ์ฝ๊ธฐ ํŽธํ•œ ์œ ๋™์  ๋ณธ๋ฌธ ํ…์ŠคํŠธ
</p>

๐Ÿ† ์‹ค์ „: ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ ์Šคํ„ฐ๋”” ์นด๋“œ + ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ธ ํŽ˜์ด์ง€

์™„์„ฑํ˜• ์Šคํ„ฐ๋”” ์นด๋“œ (๋ชจ๋“  ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ ๊ธฐ๋ฒ• ์ ์šฉ)

// components/StudyCard.tsx โ€” ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ ์‹ฌํ™” ๋ฒ„์ „
 
interface StudyCardProps {
  title: string;
  description: string;
  topic: string;
  memberCount: number;
  rating: number;
  rank: number;
}
 
export default function StudyCard({ title, description, topic, memberCount, rating, rank }: StudyCardProps) {
  return (
    <div className="bg-white rounded-2xl shadow-md hover:shadow-xl transition-shadow duration-300 p-5 flex flex-col">
 
      {/* ์ˆœ์œ„ ํ‘œ์‹œ - ordinal + tabular-nums */}
      <div className="flex items-center justify-between mb-3">
        <span className="text-xs font-medium text-indigo-600 bg-indigo-50 px-2 py-0.5 rounded-full">
          {topic}
        </span>
        <span className="text-sm font-bold text-amber-500 ordinal tabular-nums">
          {rank}์œ„  {/* ordinal: ์ˆœ์œ„ ํ‘œ๊ธฐ ์ตœ์ ํ™” */}
        </span>
      </div>
 
      {/* ์ œ๋ชฉ - line-clamp + text-balance */}
      <h3 className="
        font-semibold text-gray-900 text-lg
        line-clamp-2         /* 2์ค„ ๊ณ ์ • */
        text-balance         /* ์ค„ ๊ท ํ˜• ์ตœ์ ํ™” */
        mb-2
      ">
        {title}
      </h3>
 
      {/* ์„ค๋ช… - line-clamp + text-pretty */}
      <p className="
        text-gray-600 text-sm leading-relaxed
        line-clamp-3          /* 3์ค„ ๊ณ ์ • */
        text-pretty           /* orphan ๋‹จ์–ด ๋ฐฉ์ง€ */
        flex-1
      ">
        {description}
      </p>
 
      {/* ๋ฉ”ํƒ€ ์ •๋ณด - tabular-nums */}
      <div className="mt-4 pt-3 border-t border-gray-100 flex justify-between items-center">
        <div className="text-sm text-gray-500">
          <span className="tabular-nums font-medium text-gray-900">{memberCount}</span>๋ช… ์ฐธ์—ฌ
        </div>
 
        {/* ํ‰์  - tabular-nums + slashed-zero */}
        <div className="flex items-center gap-1">
          <span className="text-amber-400 text-sm">โ˜…</span>
          <span className="text-sm font-medium tabular-nums slashed-zero">
            {rating.toFixed(1)}  {/* ์†Œ์ˆ˜์  ์ž๋ฆฌ ์ •๋ ฌ */}
          </span>
        </div>
      </div>
    </div>
  );
}

๊ฒŒ์‹œ๊ธ€ ์ƒ์„ธ - prose ํ™œ์šฉ

// app/studies/[id]/posts/[postId]/page.tsx
 
export default async function PostDetailPage({ params }) {
  const post = await getPost(params.postId);
  const htmlContent = marked(post.content);
 
  return (
    <div className="max-w-4xl mx-auto px-4 py-10">
      {/* ํฌ์ŠคํŠธ ํ—ค๋” - fluid typography */}
      <header className="mb-8">
        <span className="text-sm font-medium text-indigo-600 mb-3 block">
          React ์‹ฌํ™” ์Šคํ„ฐ๋””
        </span>
 
        {/* ์ œ๋ชฉ - fluid + balance */}
        <h1 className="
          text-[clamp(1.75rem,3vw,3rem)]
          font-black text-gray-900
          text-balance
          leading-tight mb-4
        ">
          {post.title}
        </h1>
 
        {/* ๋ฉ”ํƒ€ ์ •๋ณด */}
        <div className="flex items-center gap-4 text-sm text-gray-500">
          <time className="tabular-nums">{formatDate(post.createdAt)}</time>
          <span className="tabular-nums">์กฐํšŒ {post.viewCount.toLocaleString()}ํšŒ</span>
          <span className="tabular-nums">๋Œ“๊ธ€ {post.commentCount}๊ฐœ</span>
        </div>
      </header>
 
      {/* ๋ณธ๋ฌธ - prose */}
      <div
        className="
          prose prose-lg
          dark:prose-invert
          prose-indigo
          prose-headings:text-balance
          prose-p:text-pretty
          prose-code:text-sm
          prose-pre:bg-gray-900
          max-w-none
        "
        dangerouslySetInnerHTML={{ __html: htmlContent }}
      />
 
      {/* ํ†ต๊ณ„ ํ…Œ์ด๋ธ” - tabular-nums */}
      {post.stats && (
        <div className="mt-10 p-6 bg-gray-50 rounded-2xl">
          <h3 className="font-bold text-gray-900 mb-4">์Šคํ„ฐ๋”” ์ฐธ์—ฌ ํ˜„ํ™ฉ</h3>
          <dl className="grid grid-cols-2 gap-4 sm:grid-cols-4">
            {[
              { label: '์ด ์ฐธ์—ฌ์ž', value: post.stats.members },
              { label: '์ถœ์„๋ฅ ', value: `${post.stats.attendanceRate}%` },
              { label: '๊ณผ์ œ ์™„๋ฃŒ', value: post.stats.homeworkDone },
              { label: 'ํ‰๊ท  ์ ์ˆ˜', value: post.stats.avgScore.toFixed(1) },
            ].map(({ label, value }) => (
              <div key={label} className="text-center">
                <dd className="text-2xl font-bold text-indigo-600 tabular-nums slashed-zero">
                  {value}
                </dd>
                <dt className="text-xs text-gray-500 mt-1">{label}</dt>
              </div>
            ))}
          </dl>
        </div>
      )}
    </div>
  );
}

๐Ÿ ์ด๋ฒˆ์— ๋ฐฐ์šด ๋‚ด์šฉ ์ด์ •๋ฆฌ

์œ ํ‹ธ๋ฆฌํ‹ฐ์ƒ์„ฑ๋˜๋Š” CSS์ฃผ์š” ์šฉ๋„
truncateoverflow: hidden; text-overflow: ellipsis; white-space: nowrap1์ค„ ๋ง์ค„์ž„
text-ellipsistext-overflow: ellipsisoverflow ์žˆ์„ ๋•Œ ... ํ‘œ์‹œ
line-clamp-N-webkit-line-clamp: N ์กฐํ•ฉN์ค„ ๋ง์ค„์ž„
line-clamp-noneline-clamp ํ•ด์ œ๋ฐ˜์‘ํ˜• ํ† ๊ธ€
text-balancetext-wrap: balanceํ—ค๋”ฉ ์ค„ ๊ท ํ˜•
text-prettytext-wrap: pretty๋ณธ๋ฌธ orphan ๋ฐฉ์ง€
tabular-numsfont-variant-numeric: tabular-nums์ˆซ์ž ํ…Œ์ด๋ธ” ์ •๋ ฌ
slashed-zerofont-variant-numeric: slashed-zero0 vs O ๊ตฌ๋ถ„
ordinalfont-variant-numeric: ordinal์„œ์ˆ˜ ํ‘œ๊ธฐ
font-features-[...]font-feature-settings: ...OpenType ๊ธฐ๋Šฅ
prose์ „์ฒด ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ ์Šคํƒ€์ผ ์„ธํŠธ๋งˆํฌ๋‹ค์šด ๋ Œ๋”๋ง
prose-invert๋‹คํฌ ๋ชจ๋“œ prose ์ƒ‰์ƒ๋‹คํฌ ๋ชจ๋“œ
text-[clamp(...)]font-size: clamp(...)Fluid typography

๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

Q1. ์˜์ˆ™์ด๊ฐ€ "์Šคํ„ฐ๋”” ์นด๋“œ ์ œ๋ชฉ์ด ์–ด๋–ค ์นด๋“œ๋Š” 1์ค„, ์–ด๋–ค ๊ฑด 3์ค„์ด๋ผ ๊ทธ๋ฆฌ๋“œ๊ฐ€ ๊นจ์ ธ์š”. ์ •ํ™•ํžˆ 2์ค„๋กœ ๊ณ ์ •ํ•ด ์ฃผ์„ธ์š”"๋ผ๊ณ  ์š”์ฒญํ–ˆ๋‹ค. ์–ด๋–ค ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š”๊ฐ€?

โœ… ์ •๋‹ต: line-clamp-2

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • ์›๋ฆฌ ์„ค๋ช…: line-clamp-2 ๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; ๋ฅผ ์ƒ์„ฑํ•ด. -webkit-line-clamp ๋Š” ๋ฐ•์Šค ๋‚ด์—์„œ ํ…์ŠคํŠธ๋ฅผ ์ •ํ™•ํžˆ N์ค„๋กœ ์ž˜๋ผ์ฃผ๋Š” CSS ์†์„ฑ์œผ๋กœ, ํ˜„์žฌ ๋ชจ๋“  ์ฃผ์š” ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง€์›๋ผ.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: truncate ๋Š” white-space: nowrap ์œผ๋กœ 1์ค„ ๊ฐ•์ œ์ด๊ณ , text-ellipsis ๋Š” overflow ์‹œ ... ํ‘œ์‹œ์ด์ง€๋งŒ ์ค„ ์ˆ˜๋ฅผ ์ œํ•œํ•˜์ง€๋Š” ์•Š์•„. JavaScript๋กœ ๋ฌธ์ž์—ด์„ ์ž๋ฅด๋ฉด px ๋‹จ์œ„๋กœ ๊ณ„์‚ฐํ•ด์•ผ ํ•˜๊ณ  ๋ฐ˜์‘ํ˜• ๋Œ€์‘์ด ๋ณต์žกํ•ด์ ธ.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: 1์ค„ ์ œํ•œ โ†’ truncate, N์ค„ ์ œํ•œ โ†’ line-clamp-N. ๊ทธ๋ฆฌ๋“œ ์นด๋“œ์˜ ๋†’์ด ๊ท ์ผํ™”๋Š” ํ•ญ์ƒ line-clamp-* ์˜ ์—ญํ• ์ด์•ผ.

Q2. ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ ๋Œ€์‹œ๋ณด๋“œ์— ์ฐธ์—ฌ์ž ์ˆ˜ ํ†ต๊ณ„๋ฅผ ํ‘œ ํ˜•ํƒœ๋กœ ๋ณด์—ฌ์ฃผ๋Š”๋ฐ, ์ˆซ์ž๋“ค์˜ ์†Œ์ˆ˜์  ์œ„์น˜๊ฐ€ ๋งž์ง€ ์•Š์•„์„œ ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง„๋‹ค. ์–ด๋–ค Tailwind ์œ ํ‹ธ๋ฆฌํ‹ฐ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋Š”๊ฐ€?

โœ… ์ •๋‹ต: tabular-nums

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • ์›๋ฆฌ ์„ค๋ช…: ์ผ๋ฐ˜ ํฐํŠธ์˜ ์ˆซ์ž๋Š” ๊ธ€์ž๋งˆ๋‹ค ํญ์ด ๋‹ค๋ฅธ proportional(๋น„๋ก€ํญ) ๋ฐฉ์‹์ด์•ผ. ์˜ˆ๋ฅผ ๋“ค์–ด '1'์€ ์ข๊ณ  '0'์€ ๋„“์–ด. ์ด ๋•Œ๋ฌธ์— ์„ธ๋กœ๋กœ ์ •๋ ฌ๋œ ์ˆซ์ž๋“ค์˜ ์†Œ์ˆ˜์  ์œ„์น˜๊ฐ€ ๋งž์ง€ ์•Š๊ฒŒ ๋ผ. tabular-nums (font-variant-numeric: tabular-nums)๋ฅผ ์ ์šฉํ•˜๋ฉด ๋ชจ๋“  ์ˆซ์ž(0~9)์˜ ํญ์ด ๋™์ผํ•œ tabular(ํ‘œ ํฐํŠธ) ๋ฐฉ์‹์œผ๋กœ ๋ฐ”๋€Œ์–ด ์†Œ์ˆ˜์ ์ด ์ •ํ™•ํžˆ ์„ธ๋กœ๋กœ ์ •๋ ฌ๋ผ.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: font-mono ๋ฅผ ์“ฐ๋ฉด ์ˆซ์ž ์ •๋ ฌ์€ ๋งž์ถœ ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ชจ๋…ธ์ŠคํŽ˜์ด์Šค ํฐํŠธ ์ „์ฒด๋กœ ๋ฐ”๋€Œ์–ด ๋””์ž์ธ์— ์˜ํ–ฅ์„ ์ค˜. tabular-nums ๋Š” ํ˜„์žฌ ํฐํŠธ๋ฅผ ์œ ์ง€ํ•˜๋ฉด์„œ ์ˆซ์ž ํญ๋งŒ ์กฐ์ •ํ•˜๋Š” ๋” ์ •๊ตํ•œ ํ•ด๊ฒฐ์ฑ…์ด์•ผ.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: ๊ธˆ์•ก/ํ†ต๊ณ„ ํ…Œ์ด๋ธ” = tabular-nums ์„ธํŠธ. ์ˆซ์ž ์ปฌ๋Ÿผ์€ ํ•ญ์ƒ tabular-nums text-right ์กฐํ•ฉ์œผ๋กœ ๋งˆ๋ฌด๋ฆฌํ•ด.

Q3. ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ์— ์Šคํ„ฐ๋”” ๊ณต์ง€์‚ฌํ•ญ์„ ๋งˆํฌ๋‹ค์šด์œผ๋กœ ์ž‘์„ฑํ•ด์„œ HTML๋กœ ๋ Œ๋”๋งํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•œ๋‹ค. h1, h2, p, ul, code ๋“ฑ์— ์ž๋™์œผ๋กœ ์ฝ๊ธฐ ์ข‹์€ ์Šคํƒ€์ผ์„ ์ ์šฉํ•˜๋ ค๋ฉด ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ•˜๋Š”๊ฐ€?

โœ… ์ •๋‹ต: @tailwindcss/typography ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์น˜ ํ›„ prose ํด๋ž˜์Šค ์ ์šฉ

<div className="prose prose-lg dark:prose-invert prose-indigo max-w-none"
     dangerouslySetInnerHTML={{ __html: htmlContent }} />

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • ์›๋ฆฌ ์„ค๋ช…: Tailwind๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ Preflight(CSS reset)๋กœ ๋ชจ๋“  HTML ์š”์†Œ๋ฅผ ์ดˆ๊ธฐํ™”ํ•ด. h1๊ณผ p๊ฐ€ ๊ฐ™์€ ํฌ๊ธฐ์ฒ˜๋Ÿผ ๋ณด์ด๋Š” ์ด์œ ๊ฐ€ ์ด๊ฒƒ ๋•Œ๋ฌธ์ด์•ผ. @tailwindcss/typography ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ์ œ๊ณตํ•˜๋Š” prose ํด๋ž˜์Šค๋Š” ์ด ์ดˆ๊ธฐํ™”๋ฅผ ๋˜๋Œ๋ ค์„œ HTML ๋ฌธ์„œ ์š”์†Œ๋“ค์—๊ฒŒ ํ•ฉ๋ฆฌ์ ์ด๊ณ  ์•„๋ฆ„๋‹ค์šด ๊ธฐ๋ณธ ์Šคํƒ€์ผ์„ ๋ถ€์—ฌํ•ด. ์ œ๋ชฉ ๊ณ„์ธต, ๋ฌธ๋‹จ ๊ฐ„๊ฒฉ, ์ฝ”๋“œ ๋ธ”๋ก, ์ธ์šฉ๊ตฌ, ํ…Œ์ด๋ธ” ๋“ฑ ๋งˆํฌ๋‹ค์šด์—์„œ ์ƒ์„ฑ๋˜๋Š” ๋ชจ๋“  ์š”์†Œ๋ฅผ ์ปค๋ฒ„ํ•ด.
  • ์˜ค๋‹ต ํ”ผ๋“œ๋ฐฑ: ์ง์ ‘ CSS๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ๋„ ๋ฐฉ๋ฒ•์ด์ง€๋งŒ, prose ํ”Œ๋Ÿฌ๊ทธ์ธ์€ ๋‹คํฌ ๋ชจ๋“œ, ํฌ๊ธฐ ๋ณ€ํ˜•, ์ƒ‰์ƒ ์ˆ˜์‹์–ด, prose- ์ปค์Šคํ„ฐ๋งˆ์ด์ง• ๋“ฑ ์ˆ˜์‹ญ ๊ฐ€์ง€ ๊ธฐ๋Šฅ์ด ์ด๋ฏธ ํฌํ•จ๋˜์–ด ์žˆ์–ด. ์žฌ๋ฐœ๋ช…ํ•  ์ด์œ ๊ฐ€ ์—†์–ด.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: ๋งˆํฌ๋‹ค์šด/HTML ๋ Œ๋”๋ง ์˜์—ญ โ†’ prose. ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์€ prose-{element}:{class} ํ˜•ํƒœ. ๋‹คํฌ ๋ชจ๋“œ๋Š” dark:prose-invert ๋กœ ํ•œ ๋ฐฉ์—.

๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

์˜ค๋Š˜์€ ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ์— ๋Œ€ํ•ด ๋ฐฐ์› ๋‹ค.

์†”์งํžˆ ์ฒ˜์Œ์—” "๊ทธ๋ƒฅ font-size๋ž‘ font-weight ์•„๋‹Œ๊ฐ€?" ๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.
๊ทธ๋Ÿฐ๋ฐ ์˜์ˆ™ ๋ˆ„๋‚˜๊ฐ€ "์นด๋“œ ์ œ๋ชฉ์ด 2์ค„๋กœ ๊ณ ์ •์ด ์•ˆ ๋œ๋‹ค"๊ณ  ํ•  ๋•Œ,
๋‚ด๊ฐ€ ์ฒ˜์Œ ์ œ์•ˆํ•œ ๊ฑด JavaScript๋กœ ๋ฌธ์ž์—ด์„ ์ž๋ฅด๋Š” ํ•จ์ˆ˜์˜€๋‹ค.

์˜ํ˜ธ ํ˜•์ด line-clamp-2 ๋ฅผ ๋ณด์—ฌ์คฌ์„ ๋•Œ, ๋˜ ๊ทธ ๋‘ ๊ธ€์ž์˜ ์ถฉ๊ฒฉ.

๋” ์ถฉ๊ฒฉ์ ์ธ ๊ฑด text-balance ์˜€๋‹ค. ์ œ๋ชฉ ๋งˆ์ง€๋ง‰์— ๋‹จ์–ด ํ•˜๋‚˜๋งŒ ๋ฉ๊ทธ๋Ÿฌ๋‹ˆ ๋‚จ๋Š” ๊ฒŒ
"orphan(๊ณ ์•„ ๋‹จ์–ด)" ๋ผ๋Š” ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ ์šฉ์–ด๊ฐ€ ์žˆ๋‹ค๋Š” ๊ฒƒ, ๊ทธ๊ฑธ CSS ํ•œ ๋‹จ์–ด๊ฐ€ ํ•ด๊ฒฐํ•œ๋‹ค๋Š” ๊ฒƒ.

ํƒ€์ดํฌ๊ทธ๋ž˜ํ”ผ๋Š” ๋‹จ์ˆœํžˆ ๊ธ€์ž ํฌ๊ธฐ๊ฐ€ ์•„๋‹ˆ๋ผ "๋…์ž๊ฐ€ ์–ผ๋งˆ๋‚˜ ํŽธํ•˜๊ฒŒ ์ฝ์„ ์ˆ˜ ์žˆ๋Š”๊ฐ€"๋ฅผ
ํ”ฝ์…€ ๋‹จ์œ„๋กœ ์„ค๊ณ„ํ•˜๋Š” ์ž‘์—…์ด์—ˆ๋‹ค. ์˜์ˆ™ ๋ˆ„๋‚˜๊ฐ€ ์™œ ๊ทธ๋ ‡๊ฒŒ ๋ฏธ์„ธํ•œ ๋ถ€๋ถ„์—๋„ ์‹ ๊ฒฝ ์“ฐ๋Š”์ง€
์˜ค๋Š˜ ์ฒ˜์Œ์œผ๋กœ ์กฐ๊ธˆ ์ดํ•ดํ•œ ๊ฒƒ ๊ฐ™๋‹ค.

๊ทธ๋ฆฌ๊ณ  prose ํ”Œ๋Ÿฌ๊ทธ์ธ... ๋งˆํฌ๋‹ค์šด ๋ Œ๋”๋ง์— ์ด๊ฒŒ ์—†์—ˆ๋‹ค๋ฉด ๋‚˜๋Š” h1๋ถ€ํ„ฐ blockquote๊นŒ์ง€
์ˆ˜์‹ญ ๊ฐ€์ง€ CSS๋ฅผ ์ง์ ‘ ๋‹ค ์ž‘์„ฑํ•ด์•ผ ํ–ˆ์„ ๊ฒƒ์ด๋‹ค.
์ด๋ฏธ ์ข‹์€ ๋„๊ตฌ๊ฐ€ ์žˆ๋Š”๋ฐ ๋ชจ๋ฅด๊ณ  ์ฒ˜์Œ๋ถ€ํ„ฐ ๋งŒ๋“œ๋Š” ๊ฒƒ, ๊ทธ๊ฒŒ ๊ฐ€์žฅ ํ”ผํ•ด์•ผ ํ•  ํ•จ์ • ๊ฐ™๋‹ค.


๐Ÿ”— ๋” ์•Œ์•„๋ณด๊ธฐ