ساخت تم‌های قابل‌تعویض: Theme Variables و Dark Mode

در درس قبل، با تفکیک متغیرهای سراسری و متغیرهای کامپوننت آشنا شدیم؛ و در یک مثال کوتاه، به تم‌بندی محلی هم اشاره کردیم. حالا وقتشه این مفهوم رو، مخصوصاً برای یکی از رایج‌ترین کاربردهای عملی امروزی، یعنی حالت تاریک (Dark Mode)، کامل و دقیق ببینیم.

ایده اصلی: جدا کردن «مقدار» از «نقش»

:root {
  --color-background: #ffffff;
  --color-text: #202124;
  --color-primary: #1a73e8;
}

body {
  background-color: var(--color-background);
  color: var(--color-text);
}

پایه‌ی هر سیستم تم‌بندی خوب، اینه که Propertyهای واقعی (مثل background-color روی body)، هیچ‌وقت مستقیماً به یک رنگ خام (مثل #ffffff) اشاره نکنن؛ بلکه همیشه، از طریق یک متغیر با یک نام «نقش‌محور» (مثل --color-background، نه --white)، به رنگ واقعی برسن. این تفاوت ظریف، دقیقاً همون چیزیه که تعویض کامل تم رو، بدون تغییر حتی یک خط در بقیه فایل CSS، ممکن می‌کنه.

گام دوم: بازتعریف همین متغیرها برای حالت تاریک

@media (prefers-color-scheme: dark) {
  :root {
    --color-background: #121212;
    --color-text: #e8eaed;
    --color-primary: #8ab4f8;
  }
}

همون‌طور که در فصل ۱۴ (درس ۱۴-۳) با Media Query به اسم prefers-color-scheme آشنا شدیم، این ویژگی، تشخیص می‌ده کاربر، در تنظیمات سیستم‌عامل یا مرورگرش، حالت تاریک رو ترجیح می‌ده یا نه. با بازتعریف همون متغیرهای :root، فقط داخل این Media Query، تمام صفحه، بدون نیاز به تغییر هیچ Selector دیگه‌ای، به‌طور خودکار، به حالت تاریک سوییچ می‌کنه؛ چون body، همچنان به همون var(--color-background) ارجاع می‌ده، فقط این‌بار، مقدار پشت این متغیر، عوض شده.

چرا این روش، از نوشتن یک Rule کاملاً جدا برای Dark Mode بهتر است؟

/* روش ناکارآمد: تکرار هر Selector برای هر تم */
body {
  background-color: white;
}

@media (prefers-color-scheme: dark) {
  body {
    background-color: #121212;
  }
}

.card {
  background-color: white;
}

@media (prefers-color-scheme: dark) {
  .card {
    background-color: #1e1e1e;
  }
}

بدون سیستم متغیر، برای هر عنصری که رنگ داره (که در یک پروژه واقعی، می‌تونه ده‌ها یا صدها مورد باشه)، باید یک نسخه Dark Mode جدا بنویسیم. با روش متغیرمحور، این تکرار، کاملاً حذف می‌شه؛ ما فقط یک‌بار، مقادیر رو در :root بازتعریف می‌کنیم، و تمام Selectorهایی که به این متغیرها ارجاع دادن، خودکار به‌روز می‌شن.

یک تم کامل‌تر با چند رنگ نقش‌محور

:root {
  --color-background: #ffffff;
  --color-surface: #f5f5f5;
  --color-text-primary: #202124;
  --color-text-secondary: #5f6368;
  --color-border: #dadce0;
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-background: #121212;
    --color-surface: #1e1e1e;
    --color-text-primary: #e8eaed;
    --color-text-secondary: #9aa0a6;
    --color-border: #3c4043;
  }
}

.card {
  background-color: var(--color-surface);
  border: 1px solid var(--color-border);
  color: var(--color-text-primary);
}

در این مثال، به‌جای فقط دو رنگ ساده (پس‌زمینه و متن)، یک سیستم کامل‌تر با نقش‌های مختلف (پس‌زمینه اصلی، پس‌زمینه سطح کارت‌ها، متن اصلی، متن ثانویه، رنگ حاشیه) تعریف شده؛ این سطح از تفکیک، برای پروژه‌های بزرگ‌تر، خوانایی و مقیاس‌پذیری بهتری فراهم می‌کنه.

روش دوم: تعویض تم با یک Class، به‌جای Media Query

:root {
  --color-background: #ffffff;
  --color-text: #202124;
}

[data-theme="dark"] {
  --color-background: #121212;
  --color-text: #e8eaed;
}
<html data-theme="dark">
  ...
</html>

روش دیگه، به‌جای اتکای کامل به ترجیح سیستم‌عامل کاربر (که فقط با prefers-color-scheme قابل‌تشخیصه)، استفاده از یک ویژگی HTML سفارشی (مثل data-theme، که در فصل ۱۵ با attr() هم آشنا شدیم، هرچند اینجا مستقیماً به‌عنوان Attribute Selector استفاده می‌شه، نه attr()) است؛ این روش، معمولاً همراه با یک دکمه سوییچ تم (که با جاوااسکریپت، این ویژگی رو روی html اضافه یا حذف می‌کنه) به‌کار می‌ره، و به کاربر، امکان انتخاب دستی تم رو، مستقل از تنظیمات سیستم‌عاملش، می‌ده.

ترکیب هر دو روش: احترام به ترجیح سیستم، با امکان Override دستی

:root {
  --color-background: #ffffff;
  --color-text: #202124;
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-background: #121212;
    --color-text: #e8eaed;
  }
}

[data-theme="light"] {
  --color-background: #ffffff;
  --color-text: #202124;
}

[data-theme="dark"] {
  --color-background: #121212;
  --color-text: #e8eaed;
}

در پروژه‌های حرفه‌ای، معمولاً هر دو روش، با هم ترکیب می‌شن: به‌طور پیش‌فرض، از ترجیح سیستم‌عامل کاربر (prefers-color-scheme) پیروی می‌شه؛ ولی اگه کاربر، دستی، یک انتخاب صریح (با data-theme) داشته باشه، این انتخاب دستی، اولویت پیدا می‌کنه. این دقیقاً همون منطق Cascade و Source Order (فصل ۲) است که تعیین می‌کنه کدوم Rule، در نهایت برنده بشه.

نکات مهم

  • پایه سیستم تم‌بندی، جدا کردن مقدار رنگ خام از نقش آن با نام‌گذاری معنایی (مثل --color-background به‌جای --white) است.
  • بازتعریف همان مجموعه متغیرها، داخل یک Media Query یا یک Attribute Selector، تعویض کامل تم را، بدون تغییر Ruleهای واقعی، ممکن می‌کند.
  • prefers-color-scheme، ترجیح سیستم‌عامل کاربر را تشخیص می‌دهد؛ یک Attribute Selector سفارشی (مثل data-theme)، امکان انتخاب دستی و صریح کاربر را فراهم می‌کند.

اشتباهات رایج

  • ارجاع مستقیم Propertyها به رنگ‌های خام، به‌جای متغیرهای نقش‌محور، که تعویض تم را عملاً غیرممکن یا بسیار پرزحمت می‌کند.
  • نوشتن یک Rule کاملاً جداگانه برای هر Selector در حالت تاریک، به‌جای بازتعریف متمرکز متغیرها در یک نقطه واحد.
  • نادیده گرفتن ترجیح سیستم‌عامل کاربر (prefers-color-scheme) و اجبار او به یک تم ثابت، بدون در نظر گرفتن تنظیمات دستگاهش.

جمع‌بندی

ساخت یک سیستم تم‌بندی مؤثر، بر پایه تعریف متغیرهای نقش‌محور (نه رنگ خام) بنا می‌شود؛ Propertyهای واقعی، همیشه به این متغیرها ارجاع می‌دهند، نه به مقدار مستقیم. بازتعریف همین متغیرها، چه با Media Query به اسم prefers-color-scheme (برای پیروی از ترجیح سیستم‌عامل) و چه با یک Attribute Selector سفارشی (برای انتخاب دستی کاربر)، امکان تعویض کامل و یک‌جای تم صفحه را، بدون هیچ تغییری در بقیه فایل CSS، فراهم می‌کند.