EduFlow
إتقان CSS الحديثة
⁦0%⁩

الوحدة 3 · متجاوب ومتين

الفاتح والداكن باستخدام light-dark()

17 دقراءة 4 دبيئة تجريب الكود

الدرس في لمحة

أطلق ثيمًا فاتحًا وداكنًا باستخدام color-scheme وlight-dark()، وأضف تجاوزًا من المستخدم يتغلّب على إعداد النظام، واجعل قسمًا ما داكنًا دائمًا دون تكرار الـ tokens.

  1. 1color-scheme: أخبر المتصفّح بما تدعمه
  2. 2light-dark(): token واحد بقيمتين
  3. 3مفتاح تبديل يتغلّب على إعداد النظام
  4. 4فرض الداكن على قسم واحد
  5. 5تصميم لوحة الألوان الداكنة
  6. 6دورك الآن

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

بُني الوضع الداكن في Waypoint بالطريقة الكلاسيكية: مجموعة كاملة من tokens الألوان في :root، ومجموعة كاملة ثانية داخل @media (prefers-color-scheme: dark). ونجح ذلك إلى أن حدثت ثلاثة أشياء. أضاف أحدهم token إلى المجموعة الفاتحة ونسي الداكنة. وطلب المستخدمون مفتاح تبديل، لأن نظام التشغيل لديهم داكن لكنهم يريدون الجدول فاتحًا على جهاز العرض. وأراد فريق التصميم أن يكون الـ hero داكنًا في كلا الوضعين.

كلٌّ من هذه سطر واحد مع color-scheme وlight-dark().

color-scheme: أخبر المتصفّح بما تدعمه

theme.css
:root {
  color-scheme: light dark;
}

هذا التصريح يقول: «هذه الصفحة تدعم النظامين اللونيين؛ استخدم تفضيل المستخدم». ويستجيب المتصفّح برسم واجهته الخاصة لتطابق ذلك: عناصر النماذج ومربّعات الاختيار وأشرطة التمرير وخلفية الصفحة الافتراضية ولون النص. ودونه تحصل الصفحة الداكنة رغم ذلك على قوائم <select> وأشرطة تمرير بيضاء ساطعة، وهذه أشهر علامة تفضح وضعًا داكنًا مصنوعًا يدويًا.

أضف وسم الـ meta المقابل ليعرف المتصفّح قبل أن تصل الـ CSS، فلا تومض الصفحة بالأبيض عند التحميل:

HTML
<meta name="color-scheme" content="light dark">

light-dark(): token واحد بقيمتين

تأخذ light-dark() لونين وتُرجع الأول حين يكون النظام اللوني المستخدم في العنصر فاتحًا، والثاني حين يكون داكنًا:

CSS
:root {
  color-scheme: light dark;

  --surface: light-dark(#ffffff, #0f172a);
  --text:    light-dark(#1f2937, #e5e7eb);
  --card:    light-dark(#f8fafc, #1e293b);
  --border:  light-dark(oklch(90% 0.01 280), oklch(35% 0.02 280));
}

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

كل token يُعرَّف الآن مرة واحدة، والقيمتان جنبًا إلى جنب. وإضافة token دون قيمته الداكنة تصبح ناقصة بشكل واضح عند المراجعة. light-dark() للألوان ضمن Baseline 2024 وتعمل في كل المتصفّحات الحالية. أما تمرير صورتين إليها (مثل التدرّجات أو url()) فلم يدخل Baseline إلا في سبتمبر 2026، وهي لا تقبل الأطوال ولا غيرها من القيم أبدًا، لذا أبقِ لهذه الحالات media query prefers-color-scheme في الوقت الحالي.

مفتاح تبديل يتغلّب على إعداد النظام

لأن كل شيء يقرأ النظام اللوني، لا يحتاج مفتاح التبديل إلا إلى تغيير خاصية واحدة على الجذر:

CSS
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }

دون data-theme يحتفظ الجذر بـ light dark ويتبع نظام التشغيل. ومع data-theme="dark" يُفرض الداكن، ويتبعه كل token. وكود JavaScript لعنصر تحكّم ثلاثي صغير جدًا:

theme-toggle.js
const select = document.querySelector('#theme');
const saved = localStorage.getItem('theme');
if (saved) document.documentElement.dataset.theme = saved;

select.addEventListener('change', () => {
  const value = select.value; // 'system' | 'light' | 'dark'
  if (value === 'system') {
    delete document.documentElement.dataset.theme;
    localStorage.removeItem('theme');
  } else {
    document.documentElement.dataset.theme = value;
    localStorage.setItem('theme', value);
  }
});

شغّل جزء الاستعادة في script صغير مضمّن داخل <head> ليُطبَّق الثيم المحفوظ قبل الرسم الأول.

لعنصر الجذر color-scheme فاتح أو داكن أو الاثنان معًا تبعًا لنظام التشغيل. معظم العناصر ترثه. والـ hero يضبط color-scheme على dark، فيُرجع كل token من نوع light-dark داخله قيمته الداكنة، بينما تُرجع بقية الصفحة القيم الفاتحة. :root color-scheme النظام / فاتح / داكن .schedule ترث light-dark(A, B) → A .hero color-scheme: dark بطاقة الـ hero ترث الداكن light-dark(A, B) → B الـ tokens نفسها في كل مكان؛ وكل عنصر يحسمها بنظامه اللوني.
يُحسم النظام اللوني لكل عنصر، ثم تختار light-dark() قيمة

فرض الداكن على قسم واحد

color-scheme موروثة، وlight-dark() تُحسم بحسب قيمة كل عنصر. لذا لا يحتاج الـ hero الداكن دائمًا إلا إلى تصريح واحد:

CSS
.hero {
  color-scheme: dark;
  background: var(--surface);
  color: var(--text);
}

داخل الـ hero تُحسم --surface و--text و--card كلها إلى قيمها الداكنة، بما في ذلك بطاقة جلسة الـ keynote المتداخلة فيه، بينما تبقى بقية الصفحة فاتحة. والحيلة نفسها تعمل في الاتجاه المعاكس للوحة «معاينة طباعة» فاتحة داخل تطبيق داكن.

تصميم لوحة الألوان الداكنة

الوضع الداكن ليس الوضع الفاتح مقلوبًا. هذه بضع قواعد تصمد في المشاريع الحقيقية:

  • تجنّب الأسود الصافي. رمادي مزرقّ داكن جدًا مثل #0f172a أريح للعين ويترك مجالًا للارتفاع البصري (elevation).
  • أظهر الارتفاع بأسطح أفتح لا بالظلال: تجلس البطاقة على #1e293b، والنافذة المنبثقة (popover) على درجة أفتح منها. الظلال بالكاد تظهر على الخلفيات الداكنة.
  • خفّض تشبّع ألوان التمييز. ألوان المسارات الحيوية من الدرس السابق تهتزّ على الخلفية الداكنة. وoklch(from var(--track) 72% calc(c * 0.8) h) ترفع الإضاءة وتخفّف التشبّع للأسطح الداكنة.
  • أعد فحص التباين في النظامين. الزوج الذي ينجح في الوضع الفاتح لا يضمن شيئًا في الداكن.

لا تحتاج إلى تغيير نظام التشغيل لاختبار أيٍّ من هذا. تستطيع DevTools في Chrome محاكاة prefers-color-scheme: dark من لوحة Rendering، ولدى Firefox مفتاح في الـ Inspector. اختبر حالات المفتاح الثلاث والـ hero في كلٍّ منها، وافحص عناصر النماذج: فهي أول مكان يظهر فيه غياب color-scheme.

دورك الآن

يستخدم التمرين النمط القديم: tokens مع تجاوز بـ prefers-color-scheme، ولا مفتاح تبديل. أعد كتابة الـ tokens بـ light-dark()، واربط data-theme على الجذر، واجعل الـ hero داكنًا في الوضعين. تضبط الفحوصات data-theme وتقرأ الألوان المحسوبة. وبعدها ستتأكّد من أن من يتنقّلون بلوحة المفاتيح يرون أين هم، في الثيمين كليهما.

أهم النقاط

  • color-scheme: light dark تخبر المتصفّح بأن الصفحة تدعم النظامين اللونيين، فتتبع عناصر النماذج وأشرطة التمرير والألوان الافتراضية إعداد المستخدم.
  • light-dark(A, B) تُرجع A أو B بحسب النظام اللوني المستخدم في العنصر، فيُعرَّف كل token مرة واحدة بدل مكانين.
  • مفتاح التبديل الثلاثي (النظام، فاتح، داكن) لا يحتاج إلا إلى ضبط color-scheme على الجذر؛ وكل قيمة light-dark() تتبعه.
  • لأن light-dark() تُحسم لكل عنصر على حدة، فإن color-scheme: dark على قسم واحد تجعل ذلك القسم داكنًا بينما تبقى بقية الصفحة فاتحة.

قراءات إضافية

تحقّق من فهمك

3 أسئلة

السؤال 1 من 3

كتبت --surface: light-dark(white, #0f172a) واستخدمتها على body، لكن الصفحة تبقى بيضاء في الوضع الداكن. ما الناقص؟

السؤال 2 من 3

يجب أن تبدو الواجهة الرئيسية (hero) في Waypoint داكنة دائمًا، حتى في الوضع الفاتح. ألوانها تأتي من tokens بـ light-dark(). ما أصغر تغيير ممكن؟

السؤال 3 من 3

ما وظيفة <meta name="color-scheme" content="light dark"> في رأس الـ HTML؟

تدريب

بيئة تجريب الكود

مجموعة tokens واحدة وثلاثة ثيمات

أعد كتابة الثيم بحيث:

  1. يعلن الجذر دعمه للنظامين اللونيين كليهما، وتُعرَّف كلٌّ من --surface و--text و--card مرة واحدة بـ light-dark() (القيم الفاتحة: #ffffff و#1f2937 و#f8fafc؛ والداكنة: #0f172a و#e5e7eb و#1e293b). احذف الـ media query.
  2. data-theme="light" أو data-theme="dark" على <html> تفرض ذلك النظام؛ ودون السمة تتبع الصفحة إعداد النظام.
  3. يكون .hero داكنًا في كل الأوضاع، باستخدام الـ tokens نفسها (بلا ألوان خاصة بالـ hero).

تضبط الفحوصات data-theme على <html> وتقرأ الألوان المحسوبة.

<section class="hero">
  <h1>Waypoint Conf 2026</h1>
  <article class="session-card" id="keynote">Keynote · The Platform Is the Framework</article>
</section>
<main class="schedule">
  <article class="session-card" id="regular">Shipping View Transitions</article>
</main>جارٍ تحميل المحرّر…

Ctrl/⌘ + Enter للتشغيل · يعمل في بيئة معزولة داخل متصفّحك. لا شيء يغادر جهازك.

المعاينة

الـ console

(لا مخرجات)

الفحوص

  • data-theme=“dark” تجعل الصفحة داكنة
  • data-theme=“light” تجعل الصفحة فاتحة
  • قيمة color-scheme على الجذر تتبع مفتاح التبديل
  • دون data-theme يدعم الجذر النظامين كليهما
  • الـ hero وبطاقته يبقيان داكنين في الوضع الفاتح