الوحدة 3 · متجاوب ومتين
الفاتح والداكن باستخدام light-dark()
17 دقراءة 4 دبيئة تجريب الكود
الدرس في لمحة
أطلق ثيمًا فاتحًا وداكنًا باستخدام color-scheme وlight-dark()، وأضف تجاوزًا من المستخدم يتغلّب على إعداد النظام، واجعل قسمًا ما داكنًا دائمًا دون تكرار الـ tokens.
- 1color-scheme: أخبر المتصفّح بما تدعمه
- 2light-dark(): token واحد بقيمتين
- 3مفتاح تبديل يتغلّب على إعداد النظام
- 4فرض الداكن على قسم واحد
- 5تصميم لوحة الألوان الداكنة
- 6دورك الآن
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
بُني الوضع الداكن في Waypoint بالطريقة الكلاسيكية: مجموعة كاملة من tokens الألوان في :root، ومجموعة كاملة ثانية داخل @media (prefers-color-scheme: dark). ونجح ذلك إلى أن حدثت ثلاثة أشياء. أضاف أحدهم token إلى المجموعة الفاتحة ونسي الداكنة. وطلب المستخدمون مفتاح تبديل، لأن نظام التشغيل لديهم داكن لكنهم يريدون الجدول فاتحًا على جهاز العرض. وأراد فريق التصميم أن يكون الـ hero داكنًا في كلا الوضعين.
كلٌّ من هذه سطر واحد مع color-scheme وlight-dark().
color-scheme: أخبر المتصفّح بما تدعمه
:root {
color-scheme: light dark;
}هذا التصريح يقول: «هذه الصفحة تدعم النظامين اللونيين؛ استخدم تفضيل المستخدم». ويستجيب المتصفّح برسم واجهته الخاصة لتطابق ذلك: عناصر النماذج ومربّعات الاختيار وأشرطة التمرير وخلفية الصفحة الافتراضية ولون النص. ودونه تحصل الصفحة الداكنة رغم ذلك على قوائم <select> وأشرطة تمرير بيضاء ساطعة، وهذه أشهر علامة تفضح وضعًا داكنًا مصنوعًا يدويًا.
أضف وسم الـ meta المقابل ليعرف المتصفّح قبل أن تصل الـ CSS، فلا تومض الصفحة بالأبيض عند التحميل:
<meta name="color-scheme" content="light dark">light-dark(): token واحد بقيمتين
تأخذ light-dark() لونين وتُرجع الأول حين يكون النظام اللوني المستخدم في العنصر فاتحًا، والثاني حين يكون داكنًا:
: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 في الوقت الحالي.
مفتاح تبديل يتغلّب على إعداد النظام
لأن كل شيء يقرأ النظام اللوني، لا يحتاج مفتاح التبديل إلا إلى تغيير خاصية واحدة على الجذر:
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }دون data-theme يحتفظ الجذر بـ light dark ويتبع نظام التشغيل. ومع data-theme="dark" يُفرض الداكن، ويتبعه كل token. وكود JavaScript لعنصر تحكّم ثلاثي صغير جدًا:
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 موروثة، وlight-dark() تُحسم بحسب قيمة كل عنصر. لذا لا يحتاج الـ hero الداكن دائمًا إلا إلى تصريح واحد:
.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على قسم واحد تجعل ذلك القسم داكنًا بينما تبقى بقية الصفحة فاتحة.