يحب المطورون تحقيق النجاحات السريعة. عندما تطلب التذكرة (ticket) "إضافة الوضع الداكن" (dark mode)، يبدو المسار الأقل مقاومة واضحًا: كتابة light.css ثم dark.css والتبديل بينهما. يبدو الأمر منظمًا، ويتم إنجازه بسرعة. بالنسبة لمشروع جانبي صغير يتكون من ثلاثة مكونات، قد ينجح الأمر. ولكن بمجرد أن يتجاوز تطبيقك بضع وحدات (modules)، يتوقف الملف الثاني عن كونه ميزة ويتحول إلى عبء يتطلب منك صيانة مزدوجة.
فخ الملفين
يبدو المنطق سليمًا للوهلة الأولى. فصل الاهتمامات (Separation of concerns)، أليس كذلك؟ الأشياء الفاتحة هنا، والأشياء الداكنة هناك. تفتح ملفين في المحرر، تنسخ أنماط البطاقة (card styles) من الملف الفاتح إلى الملف الداكن، وتستبدل #ffffff بـ #1a1a1a وتنتهي المهمة.
المشكلة لا تظهر في الأسبوع الأول. المشكلة تظهر في الشهر السادس، عندما يطلب المصمم نصف قطر حواف (border radius) مختلفًا قليلاً للزر الأساسي، أو عندما يريد فريق المنتج حالة تحذير جديدة في نموذج الدفع. تقوم بتحديث ملف التنسيق الفاتح، وتلقي نظرة سريعة على ملف التنسيق الداكن. ربما تتذكر نسخ التغيير، وربما لا تفعل. هذه الفجوة هي المكان الذي تموت فيه الجودة. لم تعد تقوم بصيانة واجهة واحدة، بل تقوم بصيانة واجهتين متوازيتين تشتركان في نفس الهيكل البرمجي (HTML skeleton).
انحراف السمات أمر حتمي
لهذه الفجوة اسم بدأ فرق الواجهة الأمامية (frontend) في التعرف عليه: انحراف السمات (theme drift). يحدث هذا عندما تتطور ملفات التنسيق الخاصة بك بسرعات مختلفة. تعديل بسيط في الحشوة (padding) هنا، وتعديل في الظل هناك. يصبح الملف الداكن هو "الأخ المهمل"، أو والأسوأ من ذلك، يصبح مصدرًا للقلق. يبدأ المطورون في تجنب التغييرات لأن لمس سمة واحدة يعني البحث في ملف آخر لمضاعفة العمل.
يتراكم العبء الإدراكي (cognitive overhead) بسرعة. كنت تريد كتابة CSS مرة واحدة، وبدلاً من ذلك كتبتها مرتين، والآن تدفع "فوائد" هذا الدين في كل مرة يتغير فيها نظام التصميم (design system). تظهر الأيقونات غير محاذية في الوضع الداكن لأن شخصًا ما قام بتحديث الفجوة المرنة (flex gap) في الملف الفاتح ونسي تكرارها. تختفي حلقات التركيز (focus rings) لأن قاعدة إمكانية وصول (accessibility) جديدة لم تدرج إلا في ورقة واحدة. لا تبدو واجهة المستخدم (UI) خاطئة فحسب، بل تبدأ في الشعور بأنها معطلة.
استخدم الرموز الدلالية
الحل ليس أداة مقارنة (diff tool) أفضل أو مراجعة كود أكثر صرامة. الحل هو طريقة تفكير مختلفة في الألوان. توقف عن تنظيم أنماطك بناءً على المظهر الفعلي، وابدأ في تنظيمها بناءً على الغرض. هنا يأتي دور الرموز الدلالية (semantic tokens).
بدلاً من تخصيص خلفية بيضاء للبطاقة، خصص لها خلفية سطح (surface background). بدلاً من الاختيار بين الأسود والأبيض المائل للرمادي للنص، اختر لون نص (text color). المكون لا يعرف ولا يهتم بما إذا كان المستخدم يفضل الوضع الفاتح أو الداكن؛ هو ببساطة يطلب الرمز الذي يناسب وظيفته.
فكر في زر قياسي. في عالم الملفين، يعيش .btn في ملف التنسيق الفاتح بخلفية بيضاء وحدود داكنة. وتوأمه يعيش في ملف التنسيق الداكن بخلفية قريبة من السواد وحدود أفتح. هذا ضعف كمية الكود لزر واحد. باستخدام الرموز (tokens)، يمتلك .btn إعلانًا واحدًا: الخلفية هي var(--color-surface-secondary) والحدود هي var(--color-border-default). القيم نفسها تعيش في الجذر (root). عندما يكون الموقع في الوضع الفاتح، تتحول --color-surface-secondary إلى شيء مثل #f8f9fa. وفي الوضع الداكن، تتحول نفس الرمز إلى #2d2d2d. مكون الزر لا يتغير أبدًا، فقط البيانات التي تحته هي التي تتغير.
هذا التمييز بين الهيكل والبيانات دقيق ولكنه قوي. يحدد مكون البطاقة الخاص بك التخطيط، والتباعد، والخطوط، والارتفاع (elevation) مرة واحدة. وتحدد طبقة السمات (theme layer) لوحة الألوان. هذا الفصل هو بالضبط ما صُممت خصائص CSS المخصصة (CSS custom properties) من أجله.
كيف تتغير البنية التحتية
يعيد هذا النهج هيكلة طريقة كتابتك للأنماط بشكل جذري.
الطريقة القديمة تبدو عادةً هكذا:
- ملف تنسيق بطاقة فاتح يحدد الحشوة، ونصف القطر، والخلفية، ولون النص، والظل.
- ملف تنسيق بطاقة داكن يعيد تعريف معظم نفس الخصائص فقط لقلب الألوان.
- طبقة منطقية تقرر أي ملف تنسيق يتم تحميله أو أي فئة (class) يتم تبديلها في عنصر
body.
الطريقة الجديدة تبدو هكذا:
- ملف تنسيق بطاقة واحد يحدد التخطيط ويخصص الرموز الدلالية.
- ملف سمات واحد يحدد معنى تلك الرموز في السياق الفاتح.
- ملف سمات واحد، أو ببساطة كتلة (block) في نفس الملف، يحدد معنى تلك الرموز في السياق الداكن.
- تبديل سمة (attribute) واحد يغير طبقة القيم دون المساس بطبقة المكونات.
أنت تحافظ على استقرار الإعداد، وتغير البيانات فقط. عندما يريد المصمم تقديم سمة (theme) ثالثة، ربما وضع عالي التباين أو نسخة باللون الأزرق الليلي، فأنت لا تعيد كتابة البطاقة، بل تضيف تعييناً واحداً إضافياً إلى خريطة الرموز (token map). يظل المكون بسيطاً (dumb) ومستقراً؛ فهو لا يزال يطلب لون سطح (surface color)، والسمة هي التي تخبره بلون السطح الذي يجب استخدامه.
التبديل باستخدام سمة البيانات (Data Attribute)
يمكن أن يظل التنفيذ بسيطاً وسهل القراءة. قم بتطبيق سمة بيانات على وسم HTML الخاص بك، مثل data-theme="dark"، واجعل تعريفات الرموز (tokens) تندرج تحت نطاقها.
قم بتعيين القيم الافتراضية على :root لتجربة الوضع الفاتح حتى يتم عرض الصفحة بشكل صحيح قبل تشغيل JavaScript. ثم قم بتجاوز (override) قيم الرموز تحت [data-theme="dark"]. يقوم نص برمجي (script) صغير بمراقبة نقرة زر التبديل، ويحدث السمة، ويستجيب كل مكون في الصفحة فوراً. لا حاجة لتغيير الفئات (classes) بشكل عشوائي على العناصر الفردية، ولا حاجة لاستيراد ملف تنسيق (stylesheet) منفصل تماماً أثناء عملية العرض. المتصفح لديه المتغيرات بالفعل في الذاكرة؛ هو فقط يعيد الرسم (repaints) بالقيم الجديدة.
هذا يحافظ على نظافة الكود الخاص بك من منظور عملي للغاية. لن تضطر للبحث (grep) عبر دليلين للعثور على كل مثيل لـ .card. ولن تضطر للقلق بشأن حروب التحديد (specificity wars) بين فئات السمات المتنافسة المتراكمة على نفس العقدة (node). يظل HTML الخاص بك سهل القراءة، ويظل CSS مركزياً وقابلاً للبحث.
الأمر يتعلق بالقيم، وليس بالإصدارات
الوضع الداكن يتعلق بالقيم، وليس إصداراً ثانياً من واجهة المستخدم الخاصة بك. زوايا بطاقتك لا تصبح أكثر استدارة في الليل، وشبكتك (grid) لا تنهار لتتخذ شكلاً مختلفاً، ومقياس الخطوط (type scale) لا يحتاج إلى إيقاع جديد. الألوان فقط هي التي تتغير، وأحياناً تصبح الظلال أعمق قليلاً. التعامل مع الوضع الداكن كأنه إعادة تصميم كاملة (reskin) هو إفراط في الهندسة (over-engineering) يؤدي إلى كوابيس في الصيانة.
الفرق التي تتقن هذا الأمر تتعامل مع نظام التصميم الخاص بها كقاعدة بيانات؛ حيث تستعلم المكونات عن الخصائص بالاسم، وتوفر السمات السجلات. التبديل من الوضع الفاتح إلى الداكن هو مجرد تغيير في معلمات الاستعلام (query parameter)، وليس إعادة كتابة للمخطط (schema).
هذه العقلية هي ما يحميك من تشتت السمات (theme drift). بطاقة واحدة، زر واحد، ومصدر واحد للحقيقة (source of truth) للمسافات والأحجام. لوحة الألوان تعيش في مكان واحد، مرتبطة منطقياً، وجاهزة لأي بيئة يفضلها المستخدم.
الخلاصة الحقيقية
إذا كنت تقوم بصيانة ملفي CSS للوضع الفاتح والداكن، فأنت لا تقوم بعمل سمات (theming)، بل تقوم بالاستنساخ (cloning). انتقل إلى الرموز الدلالية (semantic tokens)، وحدد نطاقها باستخدام سمة بيانات على مستوى الجذر (root-level data attribute)، واجعل مكوناتك تطلب أدواراً (roles) بدلاً من كتابة المظهر بشكل ثابت (hardcoding). عملية إعادة الهيكلة (refactor) الأولية تتطلب جهداً، لكن البديل هو لعبة لا تنتهي من مطاردة الأخطاء عبر ملفات تنسيق متوازية. الحياة أقصر من أن تكتب نفس البطاقة مرتين.
