CSS والتخطيطات

CSS @layer: كيف تُنهي طبقات الكاسكيد حرب الخصوصية

CSS @layer: كيف تُنهي طبقات الكاسكيد حرب الخصوصية

لديك مكوّن بطاقة من مكتبة نظام تصميم: .card .card__header .card__title { font-size: 18px; } – ثلاثة أصناف في المحدِّد، لأن مؤلّف المكوّن عشّشها هكذا. تريد تجاوزها بصنف أداتي واحد، .text-lg { font-size: 20px; }، أضفته في HTML بعد أصناف البطاقة. لا يعمل الأمر. يبقى العنوان مصرًّا على 18px، رغم أنّ .text-lg يظهر لاحقًا في الكود، فتفترض بديهيًّا أنّ «الأحدث ينبغي أن يفوز». هذا ليس خللًا في المتصفح – ثلاثة أصناف في المحدِّد تتغلّب على صنف واحد، بصرف النظر عن الترتيب في الملف. هكذا تعمل الخصوصية منذ عام 1996، وهكذا يُفترَض أن تعمل.

الحلّ الكلاسيكي هو رفع خصوصية قاعدتك الخاصة – .card .text-lg، أو عند الحاجة، !important. كلا الحلَّين يعمل موضعيًّا وينهار بنيويًّا: فكلّما أضاف أحد في الفريق قاعدة بخصوصية أعلى (أو !important إضافي)، أصبحت بقية ملف الأنماط غير قابلة للتنبؤ. هذه بالضبط المشكلة – ليست جمالية، بل معمارية – التي تحلّها طبقات الكاسكيد في CSS (CSS Cascade Layers).

محور جديد لفضّ النزاعات، لا وسيلة أخرى لرفع الأولوية

الأمر الجوهري الذي يجب فهمه: @layer ليست أداة أخرى لزيادة أولوية قاعدة، مثل !important أو إضافة صنف آخر إلى المحدِّد. إنها مرحلة منفصلة في خوارزمية الكاسكيد، تُحسَم قبل الخصوصية، لا بجانبها. الترتيب الكامل الذي يحسم به المتصفح النزاع بين قاعدتين من CSS يبدو (مع تبسيط بتجاهل المصدر origin) كالتالي:

  1. الطبقة (@layer) – القاعدة من طبقة أُعلنت لاحقًا تفوز على قاعدة من طبقة أُعلنت سابقًا، بصرف النظر عن خصوصية المحدِّدات بداخلها.
  2. الخصوصية – تُحسَم فقط حين تكون القاعدتان في الطبقة نفسها (أو كلتاهما خارج أيّ طبقة أصلًا).
  3. الترتيب في المصدر – الحَكَم الأخير، حين يتعادل المعياران السابقان.

هذا يجعل صنفًا واحدًا مثل .text-lg في طبقة لاحقة قادرًا على التغلّب على محدِّد بثلاثة أصناف في طبقة سابقة – لأنّ المواجهة لا تصل أصلًا إلى مرحلة مقارنة الخصوصية. الطبقة تحسم الأمر مسبقًا.

css

/* تُعلن ترتيب الطبقات مرّة واحدة، مسبقًا - لا يجب أن يطابق
الترتيب الذي يظهر فيه محتواها فعليًا في الملفات */
@layer reset, base, components, utilities;
@layer components {
.card .card__header .card__title {
font-size: 18px;
}
}
@layer utilities {
.text-lg {
font-size: 20px;
}
}

هذا الإعلان الفارغ @layer reset, base, components, utilities; أعلى الملف ليس مجرّد شكليّة – إنه تحديد صريح لترتيب الأولويات، قبل أن تحصل أيّ من هذه الطبقات على محتوى فعلي. بفضل ذلك، يصبح ترتيب الطبقات مستقلًّا عن ترتيب الاستيرادات أو الملفات – فقد تظهر components فعليًّا في الكود قبل utilities، ومع ذلك تفوز utilities دائمًا، لأنّ هذا الترتيب أُعلن منذ البداية.

مثال عملي: إعادة ضبط، ومكوّنات، وأدوات لا تتصارع أبدًا فيما بينها

عمليًّا، هذا النمط – إعادة الضبط (reset) أدنى من المكوّنات، والمكوّنات أدنى من الأصناف الأداتية – هو تمامًا كيفية عمل نظام الطبقات في Tailwind منذ الإصدار 3.1: يجب أن يفوز الصنف الأداتي دائمًا على قاعدة المكوّن، بصرف النظر عن عدد الأصناف في محدِّد ذلك المكوّن، لأنّ هذا بالضبط سبب وجود الأصناف الأداتية أصلًا.

css

@layer reset, base, components, utilities;
@layer reset {
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
}
@layer base {
body {
font-family: system-ui, sans-serif;
color: #1a1a1a;
}
}
@layer components {
.card {
border: 1px solid #e0e0e0;
border-radius: 12px;
padding: 16px;
}
.card .card__header .card__title {
font-size: 18px;
font-weight: 600;
}
}
@layer utilities {
.text-lg {
font-size: 20px !important;
}
.mt-4 {
margin-top: 16px;
}
}

لاحظ أنّ !important في .text-lg داخل طبقة utilities هنا زائد عن الحاجة – فالطبقة توفّر الأولوية أصلًا، دون الحاجة إلى استخدام أثقل سلاح في CSS. هذا مثال جيّد على سبب تقليص طبقات الكاسكيد فعليًّا لعدد استخدامات !important في الكود: تتوقّف عن الحاجة إليه كحلّ بديل لمشكلة الخصوصية، لأنّ لديك الآن أداة مصمَّمة بالضبط لهذه المهمّة.

الآلية نفسها تحلّ مشكلة ثانية شائعة بالقدر نفسه – مكتبة CSS خارجية لا تتحكّم في محدِّداتها وتريد أن تكون أولويتها دائمًا أدنى من أنماطك الخاصة، دون رفع خصوصية قواعدك الخاصة:

css

@import url("some-ui-library.css") layer(vendor);
@layer vendor, base, components, utilities;

تنتقل المكتبة كلّها إلى طبقة vendor، الموضوعة في بداية الترتيب – لذا فإن base وcomponents وutilities الخاصة بك ستتغلّب عليها دائمًا، بصرف النظر عن مدى عدوانية بناء تلك المكتبة لمحدِّداتها.

الفخّ الذي يكسر البديهة: الأنماط بلا طبقة تفوز على كلّ الطبقات

هذا التفصيل الأكثر إغفالًا، والأكثر أثرًا في الوقت نفسه من الآلية بأكملها: القاعدة التي لا تنتمي إلى أيّ طبقة تفوز على قاعدة في أيّ طبقة كانت – بالنسبة للتصريحات العادية غير المُشفَّرة بـ!important. الأمر ليس «تخسر لأنها في طبقة غير مرئية في النهاية» – بل تفوز دائمًا، دون قيد أو شرط، على أيّ قاعدة داخل طبقة، حتى لو كانت تلك الأخيرة في طبقة أُعلنت كأعلى أولوية.

css

@layer utilities {
.text-lg {
font-size: 20px;
}
}
/* هذه القاعدة، رغم أنها ليست ضمن أيّ طبقة وتبدو مثل
استثناء عرضي منفرد - تفوز على .text-lg أعلاه،
بصرف النظر عن موضعها في الملف */
.title {
font-size: 16px;
}

هذا السلوك مقصود – وبفضله يصبح التبنّي التدريجي لطبقات الكاسكيد في مشروع كبير قائم آمنًا: كلّ CSS «القديم» الذي لم يُنقَل بعد إلى أيّ طبقة يحتفظ افتراضيًّا بأعلى أولوية، فلا يبدأ فجأة بالخسارة أمام طبقات أُضيفت حديثًا. لكن هذا السلوك نفسه مصدر لأكثر الأخطاء إرباكًا في الفرق التي تتبنّى @layer جزئيًّا: قاعدة واحدة تُركت خطأً خارج أيّ طبقة – خطأ إملائي، أو استيراد منسي، أو كتلة <style> أدرجتها مكتبة طرف ثالث – ستتغلّب على نظام الأولويات المصمَّم بعناية كلّه، دون أيّ تحذير. النتيجة العملية: إن قرّرت اعتماد طبقات الكاسكيد، ضع كلّ شيء بلا استثناء داخل طبقة ما، بما في ذلك إعادة الضبط والأنماط الأساسية – لا تترك أيّ قاعدة «خارج» النظام، لأنها هي، لا طبقة utilities، من سيفوز في كلّ نزاع.

فخّ ثانٍ: ‎!important تعكس ترتيب الطبقات

إذا لجأت إلى !important داخل طبقة، ينعكس ترتيب الأولويات بين الطبقات. بالنسبة للتصريحات العادية، تفوز الطبقة المُعلَنة لاحقًا. أمّا بالنسبة للتصريحات المصحوبة بـ!important، فتفوز الطبقة المُعلَنة سابقًا.

css

@layer reset, components;
@layer reset {
.card { padding: 0 !important; }
}
@layer components {
.card { padding: 16px !important; }
}
/* يفوز reset (0px)، لأنّ الأسبقية مع !important تكون
للطبقة المُعلَنة سابقًا - عكس القاعدة تمامًا
السارية دون !important */

هذا ليس نزوة في المواصفة – إنه امتداد متّسق لقاعدة كانت موجودة أصلًا في المصادر (origins): كانت أوراق أنماط !important الخاصة بالمستخدم تتغلّب دائمًا على !important الخاصة بمؤلّف الصفحة، عكس ما يحدث في التصريحات العادية. الخلاصة العملية: !important داخل نظام الطبقات قادرة على مفاجأة حتى من يفهم جيّدًا الترتيب المعتاد لـ@layer – سبب إضافي وجيه لاعتبار !important أداة تلجأ إليها بوعي ونادرًا، لا وسيلة افتراضية للفوز في أيّ نزاع.

الفخاخ والممارسات الجيدة

  • صفر قواعد خارج الطبقات، إذا أردت للنظام أن يكون قابلًا للتنبؤ. كما رأينا أعلاه، قاعدة واحدة فقط بلا @layer تتغلّب على نظام الطبقات بأكمله بالنسبة للتصريحات العادية – الاتّساق هنا أهمّ من راحة إضافة «قاعدة صغيرة واحدة فقط» بشكل عابر.
  • الخصوصية لا تزال سارية داخل الطبقة الواحدة. تُلغي @layer حروب الخصوصية بين الطبقات، لكن داخل الطبقة الواحدة لا تزال قاعدتان تحسمان النزاع كما كان الحال دائمًا – فتسمية الأصناف بشكل منطقي وتجنّب التعشيش العميق للمحدِّدات لا يزالان يستحقّان العناء.
  • أعلن ترتيب الطبقات صراحةً، في أعلى الملف، عبر @layer name1, name2, ...; فارغة. هذا يُحرِّرك من الاعتماد على ترتيب استيراد الملفات – فترتيب الأولويات يُحدَّد مرّة واحدة، بوضوح، في مكان واحد، بصرف النظر عن الترتيب الذي تُحمَّل فيه ملفات CSS فعليًّا لاحقًا.
  • دعم المتصفحات لم يعد سببًا للحذر. بلغت طبقات الكاسكيد مرتبة Baseline (متاحة على نطاق واسع) منذ عام 2022 – Chrome 99، وFirefox 97، وSafari 15.4 – فيمكن اعتمادها دون @supports في أيّ مشروع جديد.

الخلاصة

لا تُنافس @layer الخصوصية – بل تعمل في مرحلة مختلفة تمامًا من خوارزمية الكاسكيد، أبكر منها، وهذا ما يسمح لصنف أداتي واحد بالتغلّب بوعي وقابلية للتنبؤ على محدِّد بثلاثة أصناف متداخلة، دون رفع الأولوية ودون !important. إنها أداة معمارية حقيقية لفصل إعادة الضبط، والأنماط الأساسية، والمكوّنات، والأصناف الأداتية إلى طبقات ذات ترتيب أهمية مُعلَن صراحةً – لكنّ قاعدتين منها تكسران البديهة بشدّة تستحقّان أن تُحفَظا منفصلتين: النمط الخارج عن أيّ طبقة يفوز على كلّ طبقة، و!important داخل الطبقات تعكس ترتيب أولوياتها. تجاهل أيّ من هاتين الحقيقتين لن ينتهي بخطأ في وحدة التحكّم – بل بـCSS يعمل بشكل مختلف عمّا يوحي به الترتيب الظاهري المنطقي للطبقات في الملف.