๐ŸŒ™ Tailwind 7์žฅ: ๋‹คํฌ ๋ชจ๋“œ ๊ตฌํ˜„

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

๐Ÿ“‹ ๊ฐœ์š”

Tailwind dark variant ๋กœ ๋‹คํฌ ๋ชจ๋“œ ์™„๋ฒฝ ๊ตฌํ˜„ โ€” class ์ „๋žต๋ถ€ํ„ฐ ์‹œ์Šคํ…œ ์„ค์ • ์—ฐ๋™๊นŒ์ง€

๐Ÿ“‹ ๋ชฉ์ฐจ


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

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 18๋ถ„

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

  • dark: variant ๋ฅผ ์ž์œ ๋กญ๊ฒŒ ์จ์„œ ๋‹คํฌ ๋ชจ๋“œ ์Šคํƒ€์ผ์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค
  • class ์ „๋žต์œผ๋กœ JS ๊ธฐ๋ฐ˜ ๋‹คํฌ ๋ชจ๋“œ ํ† ๊ธ€์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค
  • ๋‹คํฌ ๋ชจ๋“œ์—์„œ ์ƒ‰์ƒ ๊ณ„์ธต์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค

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

  • ๐ŸŽจ ์˜์ˆ™ (๋””์ž์ด๋„ˆ): "์˜์ฒ  ๋‹˜, ์ € ํ”ผ๊ทธ๋งˆ์— ๋‹คํฌ ๋ชจ๋“œ๋„ ๋‹ค ๊ทธ๋ ค๋†จ์–ด์š”. ์ด์ œ ๊ตฌํ˜„ ๋ถ€ํƒ๋“œ๋ ค๋„ ๋ ๊นŒ์š”?"
  • ๐Ÿฃ ์˜์ฒ : "...๋‹คํฌ ๋ชจ๋“œ์š”? ๊ทธ๊ฒŒ ์–ผ๋งˆ๋‚˜ ๊ฑธ๋ฆด์ง€..."
  • ๐Ÿฆ ์˜ํ˜ธ (๋ฆฌ๋“œ): "์˜์ฒ  ๋‹˜, Tailwind ์“ฐ๊ณ  ์žˆ์œผ๋ฉด ์ƒ๊ฐ๋ณด๋‹ค ๋นจ๋ฆฌ ๋ผ์š”. bg-white dark:bg-gray-900 ์ด๋Ÿฐ ์‹์œผ๋กœ dark: ์ ‘๋‘์‚ฌ๋งŒ ๋ถ™์ด๋ฉด ๋˜๊ฑฐ๋“ ์š”."
  • ๐Ÿฃ ์˜์ฒ : "์•„, ๋ฐ˜์‘ํ˜•์ฒ˜๋Ÿผ ์ ‘๋‘์‚ฌ ๋ฐฉ์‹์ด๊ตฐ์š”?"
  • ๐Ÿฆ ์˜ํ˜ธ: "์ •ํ™•ํ•ด์š”. ์ด๋ฏธ ๋ฐ˜์‘ํ˜• ํ–ˆ์œผ๋ฉด ๋‹คํฌ ๋ชจ๋“œ๋Š” ๊ธˆ๋ฐฉ์ด์—์š”."

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

๋‹คํฌ ๋ชจ๋“œ๋Š” ์ด์ œ ์„ ํƒ์ด ์•„๋‹Œ ํ•„์ˆ˜์•ผ. iOS, Android, macOS, Windows ๋ชจ๋‘ ์‹œ์Šคํ…œ ๋‹คํฌ ๋ชจ๋“œ๋ฅผ ์ง€์›ํ•˜๊ณ , ์‚ฌ์šฉ์ž๋“ค์€ ์ž์‹ ์ด ์„ ํ˜ธํ•˜๋Š” ๋ชจ๋“œ๋ฅผ ๊ธฐ๋Œ€ํ•ด.

๋‹จ์ˆœํžˆ "๋ฐฐ๊ฒฝ์„ ์–ด๋‘ก๊ฒŒ ํ•˜๋Š” ๊ฒƒ" ์ด ์•„๋‹ˆ์•ผ. ๋ˆˆ์˜ ํ”ผ๋กœ๋„ ๊ฐ์†Œ, ๋ฐฐํ„ฐ๋ฆฌ ์ ˆ์•ฝ (OLED ํ™”๋ฉด), ์•ผ๊ฐ„ ์‚ฌ์šฉ ํŽธ์˜์„ฑ ๋“ฑ ์‹ค์งˆ์ ์ธ UX ์ด์ ์ด ์žˆ์–ด.

์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ์— ๋‹คํฌ ๋ชจ๋“œ๊ฐ€ ์—†์œผ๋ฉด? ์•ผ๊ฐ„์— ์Šคํ„ฐ๋”” ๊ฒ€์ƒ‰ํ•˜๋‹ค๊ฐ€ ๋ˆˆ์ด ํ„ฐ์ง€๋Š” ์‚ฌ์šฉ์ž๋“ค์ด ์ดํƒˆํ•ด.


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

๋‹คํฌ ๋ชจ๋“œ = ๋ฃธ ๋ผ์ดํŠธ ์Šค์œ„์น˜

๋ฐฉ์— ์ผ๋ฐ˜ ์กฐ๋ช…๊ณผ ๋ฌด๋“œ ๋“ฑ ๋‘ ๊ฐ€์ง€๊ฐ€ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ด. dark: ํด๋ž˜์Šค๋Š” "๋ฌด๋“œ ๋“ฑ ์ผœ์กŒ์„ ๋•Œ ์ด ์ƒ‰์ƒ์œผ๋กœ ๋ฐ”๊ฟ”" ๋ผ๋Š” ์ง€์‹œ์•ผ.

๋ผ์ดํŠธ ๋ชจ๋“œ (์ผ๋ฐ˜ ์กฐ๋ช…): bg-white text-gray-900
๋‹คํฌ ๋ชจ๋“œ (๋ฌด๋“œ ๋“ฑ): dark:bg-gray-900 dark:text-white

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ
dark: ๋Š” hover:, md: ์™€ ๊ฐ™์€ variant. "๋‹คํฌ ๋ชจ๋“œ์ผ ๋•Œ ์ด ์Šคํƒ€์ผ์„ ์ ์šฉํ•ด."


๐ŸŒ™ Tailwind ๋‹คํฌ ๋ชจ๋“œ ๋‘ ๊ฐ€์ง€ ์ „๋žต

์ „๋žต 1: media ๋ฐฉ์‹ (์‹œ์Šคํ…œ ์„ค์ • ์ž๋™ ๊ฐ์ง€)

/* tailwind.config ์—†์ด ๊ธฐ๋ณธ๊ฐ’: prefers-color-scheme ์ž๋™ ๊ฐ์ง€ */
/* Tailwind v4 ์—์„œ๋Š” @import "tailwindcss" ๋งŒ ํ•˜๋ฉด ๋ฏธ๋””์–ด ๊ธฐ๋ฐ˜์ด ๊ธฐ๋ณธ */
<!-- ์‹œ์Šคํ…œ์ด ๋‹คํฌ ๋ชจ๋“œ์ผ ๋•Œ ์ž๋™์œผ๋กœ dark: ์ ์šฉ -->
<div class="bg-white dark:bg-gray-900">
  <p class="text-gray-900 dark:text-white">ํ…์ŠคํŠธ</p>
</div>

์žฅ์ : ๋ณ„๋„ JS ๋กœ์ง ์—†์ด ์‹œ์Šคํ…œ ์„ค์ •์— ์ž๋™ ์—ฐ๋™
๋‹จ์ : ์‚ฌ์šฉ์ž๊ฐ€ ์•ฑ ์•ˆ์—์„œ ์ง์ ‘ ํ† ๊ธ€ ๋ถˆ๊ฐ€

์ „๋žต 2: class ๋ฐฉ์‹ (์ˆ˜๋™ ์ œ์–ด โ€” ๊ถŒ์žฅ)

/* CSS ํŒŒ์ผ์— ์„ค์ • */
@import "tailwindcss";
@variant dark (&:where(.dark, .dark *));

๋˜๋Š” tailwind.config.js:

// tailwind.config.js
module.exports = {
  darkMode: 'class',  // 'media' ๋Œ€์‹  'class' ์‚ฌ์šฉ
  // ...
}
<!-- html ๋˜๋Š” body ์— .dark ํด๋ž˜์Šค๊ฐ€ ๋ถ™์œผ๋ฉด dark: ์ ์šฉ -->
<html class="dark">
  <body>
    <div class="bg-white dark:bg-gray-900">๋‹คํฌ ๋ชจ๋“œ ์ ์šฉ๋จ</div>
  </body>
</html>

์žฅ์ : ์‚ฌ์šฉ์ž๊ฐ€ ์•ฑ ๋‚ด์—์„œ ํ† ๊ธ€ ๊ฐ€๋Šฅ, localStorage ์ €์žฅ์œผ๋กœ ์„ค์ • ์œ ์ง€
๋‹จ์ : JS ๋กœ .dark ํด๋ž˜์Šค ํ† ๊ธ€ ๋กœ์ง ํ•„์š”

๐Ÿ“Œ ์‹ค๋ฌด์—์„œ๋Š” class ๋ฐฉ์‹์ด ํ‘œ์ค€์ด์•ผ. ์‚ฌ์šฉ์ž์—๊ฒŒ ์„ ํƒ๊ถŒ์„ ์ฃผ๋Š” ๊ฒŒ ๋” ์ข‹์€ UX ๊ฑฐ๋“ .


๐ŸŽจ ๋‹คํฌ ๋ชจ๋“œ ์ƒ‰์ƒ ์„ค๊ณ„ ์›์น™

๋‹คํฌ ๋ชจ๋“œ๋Š” ๋‹จ์ˆœํžˆ ์ƒ‰์ƒ์„ ๋ฐ˜์ „์‹œํ‚ค๋Š” ๊ฒŒ ์•„๋‹ˆ์•ผ. ๋Œ€๋น„(Contrast)์™€ ๊ณ„์ธต(Hierarchy) ์„ ์œ ์ง€ํ•ด์•ผ ํ•ด.

๋ผ์ดํŠธ ๋ชจ๋“œ vs ๋‹คํฌ ๋ชจ๋“œ ์ƒ‰์ƒ ๋Œ€์‘ํ‘œ

๋ผ์ดํŠธ ๋ชจ๋“œ                    ๋‹คํฌ ๋ชจ๋“œ
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
bg-white                   โ†’ dark:bg-gray-900   (ํŽ˜์ด์ง€ ๋ฐฐ๊ฒฝ)
bg-gray-50                 โ†’ dark:bg-gray-800   (์„น์…˜ ๋ฐฐ๊ฒฝ)
bg-gray-100                โ†’ dark:bg-gray-700   (์นด๋“œ ๋ฐฐ๊ฒฝ)
border-gray-200            โ†’ dark:border-gray-700 (ํ…Œ๋‘๋ฆฌ)
text-gray-900              โ†’ dark:text-white     (์ฃผ์š” ํ…์ŠคํŠธ)
text-gray-600              โ†’ dark:text-gray-300  (๋ณด์กฐ ํ…์ŠคํŠธ)
text-gray-400              โ†’ dark:text-gray-500  (ํžŒํŠธ ํ…์ŠคํŠธ)
bg-blue-600                โ†’ dark:bg-blue-500    (CTA ๋ฒ„ํŠผ โ€” ๋ฐ๊ฒŒ)
text-blue-600              โ†’ dark:text-blue-400  (๋งํฌ ์ƒ‰์ƒ โ€” ๋ฐ๊ฒŒ)

์ž˜๋ชป๋œ ๋‹คํฌ ๋ชจ๋“œ vs ์˜ฌ๋ฐ”๋ฅธ ๋‹คํฌ ๋ชจ๋“œ

{/* โŒ ์ž˜๋ชป๋œ ๋ฐฉ์‹: ๋‹จ์ˆœ ๋ฐ˜์ „ โ€” ๊ณ„์ธต ๊ตฌ์กฐ๊ฐ€ ์‚ฌ๋ผ์ง */}
<div className="bg-white dark:bg-black text-black dark:text-white">
  {/* ๋ฐฐ๊ฒฝ์ด ์™„์ „ ๊ฒ€์ • โ†’ ์นด๋“œ๊ฐ€ ๋ฐฐ๊ฒฝ์—์„œ ๋ถ„๋ฆฌ ์•ˆ ๋จ */}
  <div className="bg-gray-100 dark:bg-gray-900">์นด๋“œ</div>
</div>
 
{/* โœ… ์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ์‹: ๊ณ„์ธต ์œ ์ง€ */}
<div className="bg-gray-50 dark:bg-gray-900">       {/* ํŽ˜์ด์ง€ ๋ฐฐ๊ฒฝ */}
  <div className="bg-white dark:bg-gray-800">      {/* ์นด๋“œ ๋ฐฐ๊ฒฝ (๋ฐฐ๊ฒฝ๋ณด๋‹ค ๋ฐ๊ฒŒ) */}
    <div className="bg-gray-50 dark:bg-gray-700">  {/* ๋‚ด๋ถ€ ์„น์…˜ (์นด๋“œ๋ณด๋‹ค ๋ฐ๊ฒŒ) */}
      ์นด๋“œ ๋‚ด์šฉ
    </div>
  </div>
</div>

๐Ÿ’ป ์‹ค์ „: ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ ๋‹คํฌ ๋ชจ๋“œ ์ ์šฉ

๊ธฐ๋ณธ ๋ ˆ์ด์•„์›ƒ

// ํŽ˜์ด์ง€ ๋ฃจํŠธ: ๋ผ์ดํŠธ/๋‹คํฌ ๋ชจ๋“œ ๋ฐฐ๊ฒฝ ์„ค์ •
function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko">
      {/* body ์— ๋‹คํฌ ๋ชจ๋“œ ๋ฐฐ๊ฒฝ์ƒ‰ ์ ์šฉ */}
      <body className="bg-gray-50 text-gray-900 dark:bg-gray-900 dark:text-white">
        <Navbar />
        <main>{children}</main>
        <Footer />
      </body>
    </html>
  );
}

์Šคํ„ฐ๋”” ์นด๋“œ ๋‹คํฌ ๋ชจ๋“œ

function StudyCard({ title, description, tag, memberCount }: Props) {
  return (
    <div className="
      group rounded-xl border p-5 shadow-md transition-all
      hover:-translate-y-1 hover:shadow-lg
 
      {/* ๋ผ์ดํŠธ ๋ชจ๋“œ */}
      bg-white border-gray-200
 
      {/* ๋‹คํฌ ๋ชจ๋“œ */}
      dark:bg-gray-800 dark:border-gray-700
    ">
      {/* ํƒœ๊ทธ ๋ฐฐ์ง€ */}
      <span className="
        mb-3 inline-block rounded-full px-3 py-0.5 text-xs font-semibold
        bg-blue-100 text-blue-700
        dark:bg-blue-900/50 dark:text-blue-300
      ">
        {tag}
      </span>
 
      {/* ์ œ๋ชฉ */}
      <h3 className="
        mb-2 text-lg font-bold transition-colors
        text-gray-900 group-hover:text-blue-600
        dark:text-white dark:group-hover:text-blue-400
      ">
        {title}
      </h3>
 
      {/* ์„ค๋ช… */}
      <p className="mb-4 line-clamp-2 text-sm text-gray-500 dark:text-gray-400">
        {description}
      </p>
 
      {/* ํ•˜๋‹จ */}
      <div className="
        flex items-center justify-between border-t pt-4
        border-gray-100 dark:border-gray-700
      ">
        <span className="text-xs text-gray-400 dark:text-gray-500">
          ๐Ÿ‘ฅ {memberCount}๋ช…
        </span>
        <button className="
          rounded-lg px-3 py-1.5 text-xs font-medium transition-colors
          bg-blue-600 text-white hover:bg-blue-700
          dark:bg-blue-500 dark:hover:bg-blue-400
        ">
          ์ฐธ์—ฌํ•˜๊ธฐ
        </button>
      </div>
    </div>
  );
}

๋‚ด๋น„๊ฒŒ์ด์…˜ ๋ฐ” ๋‹คํฌ ๋ชจ๋“œ

function Navbar() {
  return (
    <nav className="
      sticky top-0 z-10 border-b
      bg-white/80 backdrop-blur-sm border-gray-200
      dark:bg-gray-900/80 dark:border-gray-700
    ">
      <div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-4">
        {/* ๋กœ๊ณ  */}
        <a href="/" className="font-bold text-blue-600 dark:text-blue-400">
          ๐Ÿ“š ์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ
        </a>
 
        {/* ๋ฉ”๋‰ด */}
        <div className="flex items-center gap-4">
          <a className="
            text-sm transition-colors
            text-gray-600 hover:text-blue-600
            dark:text-gray-300 dark:hover:text-blue-400
          ">
            ์Šคํ„ฐ๋”” ์ฐพ๊ธฐ
          </a>
          <DarkModeToggle />
        </div>
      </div>
    </nav>
  );
}

โš™๏ธ Next.js ์—์„œ ๋‹คํฌ ๋ชจ๋“œ ํ† ๊ธ€ ๊ตฌํ˜„

next-themes ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด Next.js ํ”„๋กœ์ ํŠธ์˜ ํ‘œ์ค€์ด์•ผ.

์„ค์น˜

npm install next-themes

์„ค์ •

// app/providers.tsx
'use client';
 
import { ThemeProvider } from 'next-themes';
 
export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <ThemeProvider
      attribute="class"        // html ์š”์†Œ์— class="dark" ๋กœ ์ ์šฉ
      defaultTheme="system"    // ๊ธฐ๋ณธ๊ฐ’: ์‹œ์Šคํ…œ ์„ค์ • ๋”ฐ๋ฆ„
      enableSystem={true}      // ์‹œ์Šคํ…œ ์„ค์ • ๊ฐ์ง€ ํ—ˆ์šฉ
    >
      {children}
    </ThemeProvider>
  );
}
// app/layout.tsx
import { Providers } from './providers';
 
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ko" suppressHydrationWarning>  {/* suppressHydrationWarning ํ•„์ˆ˜! */}
      <body className="bg-gray-50 dark:bg-gray-900">
        <Providers>
          {children}
        </Providers>
      </body>
    </html>
  );
}

๋‹คํฌ ๋ชจ๋“œ ํ† ๊ธ€ ๋ฒ„ํŠผ

// components/DarkModeToggle.tsx
'use client';
 
import { useTheme } from 'next-themes';
import { useEffect, useState } from 'react';
 
export function DarkModeToggle() {
  const { theme, setTheme } = useTheme();
  const [mounted, setMounted] = useState(false);
 
  // Hydration ๋ถˆ์ผ์น˜ ๋ฐฉ์ง€ โ€” ๋ฐ˜๋“œ์‹œ ํ•„์š”!
  useEffect(() => setMounted(true), []);
  if (!mounted) return null;
 
  return (
    <button
      onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
      className="
        rounded-lg p-2 transition-colors
        text-gray-600 hover:bg-gray-100
        dark:text-gray-400 dark:hover:bg-gray-800
      "
      aria-label="๋‹คํฌ ๋ชจ๋“œ ํ† ๊ธ€"
    >
      {theme === 'dark' ? 'โ˜€๏ธ' : '๐ŸŒ™'}
    </button>
  );
}

โš ๏ธ suppressHydrationWarning ๊ณผ mounted ์ฒดํฌ ์ด์œ : Next.js ๋Š” ์„œ๋ฒ„์—์„œ HTML ์„ ์ƒ์„ฑํ•  ๋•Œ ๋‹คํฌ ๋ชจ๋“œ ์ƒํƒœ๋ฅผ ๋ชจ๋ฅด๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ ๋ถˆ์ผ์น˜๊ฐ€ ๋ฐœ์ƒํ•ด. suppressHydrationWarning ์œผ๋กœ ๊ฒฝ๊ณ ๋ฅผ ์–ต์ œํ•˜๊ณ , useEffect ๋กœ ๋งˆ์šดํŠธ ํ›„์—๋งŒ ๋ Œ๋”๋งํ•ด์„œ ์•ˆ์ „ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•ด.


๐Ÿšจ ํ”ํ•œ ์‹ค์ˆ˜์™€ ์ฃผ์˜์‚ฌํ•ญ

์‹ค์ˆ˜ 1: ์ด๋ฏธ์ง€์— ๋‹คํฌ ๋ชจ๋“œ ์ ์šฉ

{/* โŒ ๋‹คํฌ ๋ชจ๋“œ์—์„œ ์ด๋ฏธ์ง€๊ฐ€ ๋„ˆ๋ฌด ๋ฐ๊ฒŒ ๋ณด์ž„ */}
<img src="/logo.png" alt="๋กœ๊ณ " />
 
{/* โœ… ๋‹คํฌ ๋ชจ๋“œ์—์„œ ์ด๋ฏธ์ง€ ๋ฐ๊ธฐ ์•ฝ๊ฐ„ ๊ฐ์†Œ */}
<img src="/logo.png" alt="๋กœ๊ณ " className="dark:opacity-80" />
 
{/* โœ… ๋˜๋Š” ๋‹คํฌ ๋ชจ๋“œ์šฉ ๋ณ„๋„ ์ด๋ฏธ์ง€ */}
<img
  src="/logo-light.png"
  alt="๋กœ๊ณ "
  className="dark:hidden"
/>
<img
  src="/logo-dark.png"
  alt="๋กœ๊ณ "
  className="hidden dark:block"
/>

์‹ค์ˆ˜ 2: ํ•˜๋“œ์ฝ”๋”ฉ๋œ ํฐ์ƒ‰ ๋ฐฐ๊ฒฝ ๋ฌธ์ž

{/* โŒ ๋‹คํฌ ๋ชจ๋“œ์—์„œ ํฐ ๋ฐฐ๊ฒฝ์— ํฐ ๊ธ€์ž โ†’ ์•ˆ ๋ณด์ž„ */}
<p className="text-white">ํฐ ๊ธ€์ž</p>
 
{/* โœ… ๋ผ์ดํŠธ์—์„œ ์–ด๋‘์šด ๊ธ€์ž, ๋‹คํฌ์—์„œ ๋ฐ์€ ๊ธ€์ž */}
<p className="text-gray-900 dark:text-white">์˜ฌ๋ฐ”๋ฅธ ๊ธ€์ž</p>

์‹ค์ˆ˜ 3: ์ƒ‰์ƒ ๋ฐ๊ธฐ ๋ฐฉํ–ฅ ํ˜ผ๋™

{/* โŒ ๋‹คํฌ ๋ชจ๋“œ์—์„œ ๋” ์–ด๋‘์šด ์ƒ‰์„ ์“ฐ๋Š” ์‹ค์ˆ˜ */}
<button className="bg-blue-600 dark:bg-blue-800">  {/* ๋‹คํฌ์—์„œ ๋” ์–ด๋‘์›Œ์„œ ์•ˆ ๋ณด์ž„ */}
 
{/* โœ… ๋‹คํฌ ๋ชจ๋“œ์—์„œ๋Š” ๋ฐฐ๊ฒฝ์ด ์–ด๋‘ก๊ธฐ ๋•Œ๋ฌธ์— ๋ฒ„ํŠผ์„ ์˜คํžˆ๋ ค ๋ฐ๊ฒŒ */}
<button className="bg-blue-600 dark:bg-blue-400">

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

๊ฐœ๋…์„ค๋ช…
dark: variant๋‹คํฌ ๋ชจ๋“œ์ผ ๋•Œ ์ ์šฉ๋˜๋Š” ์Šคํƒ€์ผ
media ์ „๋žต์‹œ์Šคํ…œ ์„ค์ • ์ž๋™ ๊ฐ์ง€
class ์ „๋žตhtml.dark ํด๋ž˜์Šค๋กœ ์ˆ˜๋™ ์ œ์–ด (๊ถŒ์žฅ)
์ƒ‰์ƒ ๊ณ„์ธต๋ผ์ดํŠธ: ๋ฐ์Œโ†’์–ด๋‘ , ๋‹คํฌ: ์–ด๋‘ โ†’๋ฐ์Œ
next-themesNext.js ๋‹คํฌ ๋ชจ๋“œ ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
Hydration ์ฃผ์˜suppressHydrationWarning + mounted ์ฒดํฌ

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

Q1. ์˜์ฒ ์ด๊ฐ€ ์นด๋“œ ๋ฐฐ๊ฒฝ์ƒ‰์„ "๋ผ์ดํŠธ ๋ชจ๋“œ์—์„œ ํฐ์ƒ‰, ๋‹คํฌ ๋ชจ๋“œ์—์„œ ์–ด๋‘์šด ํšŒ์ƒ‰" ์œผ๋กœ ๋งŒ๋“ค๊ณ  ์‹ถ๋‹ค. ์˜ฌ๋ฐ”๋ฅธ ํด๋ž˜์Šค๋Š”?

โœ… ์ •๋‹ต: bg-white dark:bg-gray-800

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

  • bg-white: ๊ธฐ๋ณธ(๋ผ์ดํŠธ ๋ชจ๋“œ) ํฐ์ƒ‰ ๋ฐฐ๊ฒฝ.
  • dark:bg-gray-800: ๋‹คํฌ ๋ชจ๋“œ์—์„œ ์–ด๋‘์šด ํšŒ์ƒ‰(#1f2937) ๋ฐฐ๊ฒฝ.
  • ์™œ gray-900 ์ด ์•„๋‹Œ gray-800 ์ธ๊ฐ€? ํŽ˜์ด์ง€ ์ „์ฒด ๋ฐฐ๊ฒฝ์ด dark:bg-gray-900 ์ด๋ฉด, ์นด๋“œ๋Š” ๋ฐฐ๊ฒฝ๋ณด๋‹ค ์•ฝ๊ฐ„ ๋ฐ์€ gray-800 ์„ ์จ์„œ ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ์œ ์ง€ํ•ด์•ผ ํ•ด.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "๋‹คํฌ ๋ชจ๋“œ ์ƒ‰์ƒ ๊ณ„์ธต: ํŽ˜์ด์ง€ ๋ฐฐ๊ฒฝ 900 โ†’ ์นด๋“œ 800 โ†’ ๋‚ด๋ถ€ 700."

Q2. Next.js ์—์„œ next-themes ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ suppressHydrationWarning ์„ <html> ํƒœ๊ทธ์— ์ถ”๊ฐ€ํ•ด์•ผ ํ•˜๋Š” ์ด์œ ๋Š”?

โœ… ์ •๋‹ต: ์„œ๋ฒ„์—์„œ HTML ๋ Œ๋”๋ง ์‹œ ๋‹คํฌ ๋ชจ๋“œ ์ƒํƒœ๋ฅผ ์•Œ ์ˆ˜ ์—†์–ด ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ ๋ถˆ์ผ์น˜(Hydration Mismatch)๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์ด ๊ฒฝ๊ณ ๋ฅผ ์–ต์ œํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•˜๋‹ค.

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

  • Next.js ๋Š” ์„œ๋ฒ„์—์„œ HTML ์„ ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•ด. ์ด๋•Œ ๋‹คํฌ ๋ชจ๋“œ ์„ค์ •(localStorage ์— ์ €์žฅ๋œ)์„ ์ฝ์„ ์ˆ˜ ์—†์–ด์„œ ํ•ญ์ƒ ๊ธฐ๋ณธ ํ…Œ๋งˆ๋กœ ๋ Œ๋”๋งํ•ด.
  • ํด๋ผ์ด์–ธํŠธ์—์„œ ๋งˆ์šดํŠธ ํ›„ localStorage ๋ฅผ ์ฝ์–ด ๋‹คํฌ ๋ชจ๋“œ๋ฅผ ์ ์šฉํ•˜๋ฉด, ์„œ๋ฒ„ HTML ๊ณผ ํด๋ผ์ด์–ธํŠธ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ฌ๋ผ์ ธ Hydration ์˜ค๋ฅ˜ ๋ฐœ์ƒ.
  • suppressHydrationWarning ์€ ์ด ํŠน์ • ์š”์†Œ(<html>)์—์„œ์˜ Hydration ๋ถˆ์ผ์น˜๋ฅผ React ๊ฐ€ ๋ฌด์‹œํ•˜๊ฒŒ ํ•ด. class ์†์„ฑ์ด ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ์—์„œ ๋‹ฌ๋ผ๋„ ๊ฒฝ๊ณ  ์—†์Œ.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "์„œ๋ฒ„๋Š” ๋‹คํฌ ๋ชจ๋“œ ๋ชจ๋ฅธ๋‹ค โ†’ suppressHydrationWarning ์œผ๋กœ ํ‰ํ™”๋กญ๊ฒŒ."

Q3. ๋‹คํฌ ๋ชจ๋“œ์—์„œ CTA ๋ฒ„ํŠผ(bg-blue-600)์˜ ์ƒ‰์ƒ ์ „๋žต์œผ๋กœ ์˜ฌ๋ฐ”๋ฅธ ๊ฒƒ์€?

โœ… ์ •๋‹ต: bg-blue-600 dark:bg-blue-500 ์ฒ˜๋Ÿผ ๋‹คํฌ ๋ชจ๋“œ์—์„œ ๋” ๋ฐ์€ ๊ฐ’์„ ์‚ฌ์šฉํ•œ๋‹ค.

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

  • ๋‹คํฌ ๋ชจ๋“œ์—์„œ ๋ฐฐ๊ฒฝ์ด ์–ด๋‘์›Œ์ง€๊ธฐ ๋•Œ๋ฌธ์—, ๋ฒ„ํŠผ๋„ ๋™์ผํ•œ ๋ฐ๊ธฐ๋ฅผ ์œ ์ง€ํ•˜๋ ค๋ฉด ์˜คํžˆ๋ ค ๋” ๋ฐ์€ ์ƒ‰์ƒ์„ ์จ์•ผ ๋Œ€๋น„(Contrast)๊ฐ€ ์œ ์ง€๋ผ.
  • blue-600 (๋ผ์ดํŠธ) โ†’ blue-500 (๋‹คํฌ): ์ˆซ์ž๊ฐ€ ๋‚ฎ์„์ˆ˜๋ก ๋ฐ์•„.
  • ๋ฐ˜๋Œ€๋กœ ์ƒ๊ฐํ•˜๋ฉด ์•ˆ ๋ผ: ๋‹คํฌ ๋ชจ๋“œ์— blue-700, blue-800 ์„ ์“ฐ๋ฉด ๋ฐฐ๊ฒฝ์ด ์–ด๋‘์šด๋ฐ ๋ฒ„ํŠผ๋„ ์–ด๋‘์›Œ์„œ ๊ตฌ๋ถ„์ด ์•ˆ ๋ผ.
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "๋‹คํฌ ๋ชจ๋“œ = ๋ฐฐ๊ฒฝ์ด ์–ด๋‘์›€ = ์•ž์— ์˜ค๋Š” ์š”์†Œ๋Š” ๋” ๋ฐ๊ฒŒ."

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

๋‹คํฌ ๋ชจ๋“œ๊ฐ€ ์ด๋ ‡๊ฒŒ ๊ฐ„๋‹จํ•œ ์ค„ ๋ชฐ๋ž๋‹ค. dark: ์ ‘๋‘์‚ฌ ํ•˜๋‚˜๋กœ ๋๋‚˜๋Š”๋ฐ, ๊ทธ๋™์•ˆ ์™œ ์ด๊ฑธ ์–ด๋ ต๊ฒŒ ์ƒ๊ฐํ–ˆ๋Š”์ง€ ๋ชจ๋ฅด๊ฒ ๋‹ค.

next-themes ์จ์„œ ํ† ๊ธ€ ๋ฒ„ํŠผ ๋งŒ๋“  ๊ฒƒ๋„ ์ƒ๊ฐ๋ณด๋‹ค ๊ธˆ๋ฐฉ ๋๋‹ค. suppressHydrationWarning ๋•Œ๋ฌธ์— ์ข€ ํ—ค๋งธ๋Š”๋ฐ, ์˜ํ˜ธ ๋‹˜์ด "์„œ๋ฒ„์—์„œ๋Š” ๋‹คํฌ ๋ชจ๋“œ๋ฅผ ๋ชจ๋ฅด๋‹ˆ๊นŒ ํด๋ผ์ด์–ธํŠธ์—์„œ ๋งˆ์šดํŠธ ๋œ ํ›„ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•œ๋‹ค" ๊ณ  ์„ค๋ช…ํ•ด์ฃผ๋‹ˆ๊นŒ ๋ฐ”๋กœ ์ดํ•ด๋๋‹ค.

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "๋‹คํฌ ๋ชจ๋“œ๋Š” ์ƒ‰์ƒ ๋ฐ˜์ „์ด ์•„๋‹ˆ๋ผ ๊ณ„์ธต ์œ ์ง€๋‹ค. ๋ฐ์€ ๋ฐฐ๊ฒฝ์—์„œ ์–ด๋‘์šด ํ…์ŠคํŠธ โ†’ ์–ด๋‘์šด ๋ฐฐ๊ฒฝ์—์„œ ๋ฐ์€ ํ…์ŠคํŠธ, ๊ทธ ๊ณ„์ธต์„ ๊ทธ๋Œ€๋กœ ๋’ค์ง‘์–ด."

์˜ค๋Š˜ ์˜์ˆ™ ๋‹˜์ด ํ”ผ๊ทธ๋งˆ ๋‹คํฌ ๋ชจ๋“œ ์‹œ์•ˆ ๋‹ค ๋ณด๋‚ด์ฃผ์…จ๋Š”๋ฐ, ์ด์ œ ๋ณด๋‹ˆ๊นŒ ์˜์ˆ™ ๋‹˜๋„ ๊ฐ™์€ ์›์น™์œผ๋กœ ์ƒ‰์ƒ ๊ณ„์ธต์„ ์„ค๊ณ„ํ•˜์…จ๋”๋ผ. ๋””์ž์ด๋„ˆ์™€ ๊ฐœ๋ฐœ์ž์˜ ์–ธ์–ด๊ฐ€ ๊ฐ™์•„์ง€๋Š” ์ˆœ๊ฐ„์ด ์ด๋Ÿฐ ๊ฑฐ๊ตฌ๋‚˜. ๋ฟŒ๋“ฏํ•˜๋‹ค! ์˜ค๋Š˜ ์ €๋…์€ ์‚ผ๊ฒน์‚ด ๋จน์œผ๋Ÿฌ ๊ฐ€์•ผ๊ฒ ๋‹ค. ๐Ÿฅฉ


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