مع نمو أي مشروع ويب، يتحوّل ملف الـCSS غالباً إلى فوضى يصعب فهمها أو تعديلها: أصناف متكرّرة، قيم سحرية منتشرة، و!important في كل مكان. النتيجة أنّ تعديلاً بسيطاً في اللون قد يكسر نصف الصفحة. في هذا الدليل نمرّ على ست عادات عملية تجعل أنماطك نظيفة، متوقّعة، وسهلة التوسعة — بلا أدوات معقّدة، ثم نعرض أرقاماً ودروساً مستخرجة من ملف أنماط إنتاجي حقيقي بحجم 96 كيلوبايت يخدم تطبيقاً عربياً.
1. سمِّ الأصناف بمنطق واضح
التسمية العشوائية مثل .box2 أو .red تُربك أي شخص يقرأ الكود لاحقاً (وأنت أوّلهم بعد شهر). اعتمد منهجاً ثابتاً يصف الغرض لا الشكل. أسلوب BEM شائع وبسيط: كتلة، ثم عنصر، ثم حالة.
.card— الكتلة (المكوّن نفسه)..card__title— عنصر داخل الكتلة..card--featured— تنويعة أو حالة للكتلة.
بهذا تعرف من اسم الصنف وحده أين يُستخدم وما دوره، وتتجنّب التعارضات لأن كل صنف مرتبط بكتلته.
2. اجعل القيم متغيّرات، لا أرقاماً سحرية
تكرار اللون #d99a66 في عشرين موضعاً يعني أنّ تغييره لاحقاً عملية بحث واستبدال محفوفة بالأخطاء. عرّف القيم مرّة واحدة كمتغيّرات CSS واستعملها في كل مكان:
:root { --accent: #d99a66; --radius: 14px; --space: 1rem; }
ثم: .btn { background: var(--accent); border-radius: var(--radius); }. تغيير الهوية البصرية كاملةً يصبح تعديل سطر واحد. استعملها للألوان، المسافات، أنصاف الأقطار، والظلال — أي قيمة تتكرّر.
من الواقع: سلّم متغيّرات في ملف أنماط بحجم 96 كيلوبايت
القاعدة السابقة تبدو ترفاً تنظيمياً حتى تصطدم بمتطلّب واحد يجعلها إلزامية. في أحد مشاريعنا — تطبيق ويب عربي للمتابعة الصحّية يعمل أيضاً كتطبيق أندرويد — تستطيع المستخدمة أن تختار لون الواجهة من ستّة ألوان من لوحة الإعدادات، ويُحفظ اختيارها محلياً فيعود في كل زيارة. هذه الميزة مستحيلة عملياً بلا متغيّرات: لا يوجد بحث واستبدال يعمل وقت التشغيل. ملف الأنماط عندنا 98,183 بايت (نحو 96 كيلوبايت) موزّعة على 1,099 سطراً، ومع ذلك لا تعدّل لوحة اختيار اللون منه إلا أربعة متغيّرات على العنصر الجذر.
السرّ ليس في وجود متغيّرات، بل في كونها سلّماً لا قيماً متناثرة. كتلة :root في الملف تحتوي 27 متغيّراً مرتّبة في مجموعات واضحة:
- اللون الأساسي بأربع درجات:
--rose(الأصل)،--rose-l(أفتح)،--rose-ll(أفتح كثيراً، للخلفيات والفواصل)،--rose-d(أغمق، للنصوص والعناوين على خلفية فاتحة). - خلفيات دافئة:
--creamو--warmو--sandبتفاوت تدريجي. - لونان مساندان بالسلّم نفسه:
--sageو--goldولكل منهما درجة فاتحة وأفتح. - ثلاث درجات للنصّ:
--textللنصّ الأساسي،--text-mللنصّ المتوسّط،--text-sللتسميات الثانوية الباهتة. - هندسة وظلال:
--r: 18pxو--r-s: 10pxلنصفي القطر، و--shو--sh-mلظلّ خفيف وظلّ أعمق.
لاحظ أنّ اللاحقة نفسها تحمل المعنى في كل المجموعات: -l أفتح، -ll أفتح كثيراً، -d أغمق. هذا الاتساق يعني أنك حين تكتب مكوّناً جديداً لا تفكّر في «أي درجة أستعمل؟» بل تعرف من دورها مباشرة: خلفية هادئة تعني -ll، وعنوان يعني -d. والنتيجة قابلة للقياس: الملف يحتوي 498 استدعاءً لـvar()، منها 226 لدرجات اللون الأساسي وحدها و144 لدرجات النصّ. أي أنّ أغلب القيم اللونية في الملف تمرّ عبر سبعة أسماء فقط.
وطريقة التبديل نفسها تعلّم درساً في الاقتصاد: كل سمة لونية مخزّنة كـزوج من قيمتين فقط (الأصل والغامق)، مثل #c2798a,#8c4a58 للوردي و#6a9ab5,#3a6a8c للأزرق و#9b8ab5,#6b5a8c للبنفسجي. عند الاختيار يضبط الكود المتغيّرين على العنصر الجذر، ثم يشتقّ الدرجتين الفاتحتين من اللون نفسه بشفافية محسوبة بدل تخزينهما:
root.style.setProperty('--rose', c1);
root.style.setProperty('--rose-d', c2);
root.style.setProperty('--rose-ll', `rgba(${r},${g},${b},.13)`);
root.style.setProperty('--rose-l', `rgba(${r},${g},${b},.55)`);
الفائدة مزدوجة: نخزّن قيمتين لكل سمة بدل أربع، والاشتقاق يضمن أنّ الدرجات الفاتحة متناسقة دائماً مع اللون المختار ولا تحتاج ضبطاً يدوياً لكل سمة جديدة. إضافة لون سابع تعني إضافة زوج واحد، لا مراجعة الملف كله.
المجموعة الأخيرة في :root تستحقّ وقفة لأنها ليست ألواناً: أربعة متغيّرات تلفّ دالة env() الخاصة بالمناطق الآمنة في الشاشات ذات الحواف المنحنية، مثل --safe-top: env(safe-area-inset-top, 0px). لماذا نلفّها في متغيّر بدل استعمال env() مباشرة؟ لأنها تُستعمل في عشرة مواضع داخل حسابات مركّبة: top: calc(var(--safe-top) + 8px) لشريط المزامنة، وmax-height: calc(100vh - var(--safe-top) - 92px) لقائمة المستخدمة، وpadding-bottom: var(--safe-bottom) لشريط التبويبات السفلي. اللفّ في متغيّر يعطيك اسماً قصيراً ومكاناً واحداً للتعديل لو تغيّر الأساس — وهذا مهمّ تحديداً حين يعمل الموقع نفسه داخل تطبيق أندرويد مبني بـCapacitor لا في متصفّح فقط.
الخطأ الذي كلّفنا إعادة تصميم بطاقة كاملة: لون ثابت واحد
أهمّ بطاقة في الصفحة الرئيسية — بطاقة الملخّص التي تعرض التاريخ المتوقّع والمؤشّرات الأساسية — كُتبت قبل إضافة ميزة تخصيص اللون، فحملت ألوانها الثابتة مكتوبة بالأرقام: عنوان بلون #7a6030، وقيم بلون #5a4010، وتسميات ثانوية بلون #8a7040، وخلفية مؤشّرات #fffaf3، ومؤشّر رئيسي بتدرّج وردي ثابت linear-gradient(135deg,#fdeef3,#fff8fb)، وحدود ذهبية موزّعة على خمس درجات شفافية متفاوتة من نوع rgba(192,144,80,.16).
يوم كُتبت كانت صحيحة تماماً وتبدو جميلة. المشكلة ظهرت لحظة إضافة الميزة: المستخدمة تختار الأزرق أو الأخضر، فتتلوّن الواجهة كلها إلا هذه البطاقة — تبقى ذهبية ووردية في وسط شاشة زرقاء، فتبدو كأنها مقتطعة من موقع آخر. وهذا هو جوهر العيب: لم يكن خطأً يوم كُتب، بل صار خطأً بعد شهور بسبب قرار في مكان آخر تماماً.
الإصلاح لم يكن سطراً واحداً؛ اضطررنا لإعادة بناء البطاقة على أربع طبقات كامل ألوانها من المتغيّرات: الإطار 1px solid var(--rose-ll)، والعنوان var(--rose-d)، والقيم var(--text)، والتسميات var(--text-s)، والخلفيات var(--cream) وvar(--warm). وبدل خمس درجات فواصل ذهبية متفاوتة صار الفاصل خطاً شعرياً واحداً موحّداً بـvar(--rose-ll)، فتحسّن الاتساق البصري كمكسب جانبي. وأضفنا شريطاً علوياً يتبع اللون تلقائياً بلا أي قيمة ثابتة:
.due-card::before { content:""; height:3px;
background: linear-gradient(90deg, var(--rose-l), var(--rose), var(--rose-d)); }
وبقيت في الملف نحو 260 قيمة لونية مكتوبة بالأرقام — وهذا مقبول ومقصود، لأن أغلبها ليس ألوان هوية بل ألوان دلالية ثابتة المعنى: أحمر الخطأ #c0392b، وخلفية تحذير #fff6e5، وأخضر النجاح، والأبيض الخالص. الأحمر يجب أن يبقى أحمر حتى لو اختارت المستخدمة سمة خضراء. غير المقبول أن يتسرّب بين هذه القيم لون هوية، لأنه سيكسر التخصيص بصمت.
3. رتّب الملف بترتيب ثابت
الملف المرتّب يُقرأ كقصّة. اعتمد ترتيباً واحداً والتزم به في كامل المشروع:
- المتغيّرات و
:root. - إعادة الضبط (reset) والأساسيات (
body,a, العناوين). - التخطيط العام (الحاويات، الشبكات).
- المكوّنات (الأزرار، البطاقات، النماذج).
- الأدوات المساعدة (utility classes).
- استعلامات الوسائط (media queries) في النهاية أو بجوار مكوّنها.
وداخل كل قاعدة، رتّب الخصائص منطقياً: الموضع، ثم الصندوق (الأبعاد والحشو)، ثم الطباعة، ثم الألوان والتأثيرات.
4. تجنّب !important قدر الإمكان
كل !important هو اعتراف بأنّ نظام الأولويات (specificity) خرج عن السيطرة. وحين تحتاج لكسر !important، لن تجد إلا !important أقوى — وهكذا تدخل في سباق تسلّح. الحل: اعتمد محدِّدات بسيطة ومسطّحة (صنف واحد غالباً)، وتجنّب التداخل العميق مثل header nav ul li a الذي يرفع الأولوية بلا داعٍ. كلّما قلّت الأولوية، سهُل تجاوزها لاحقاً بشكل نظيف.
هذا قابل للقياس أيضاً: في ملف الأنماط الإنتاجي الذي نتحدّث عنه، ورد !important ثماني مرّات فقط في 1,099 سطراً — أي أقلّ من واحد لكل 130 سطراً، ومعظمها في موضعين مشروعين سنراهما في نهاية المقال. النسبة نفسها هي المؤشّر: إذا وجدت !important في كل عشرة أسطر فالمشكلة في بنية المحدّدات لا في القاعدة التي تحاول تجاوزها.
!important، توقّف واسأل: «أي محدِّد يطغى على أسلوبي؟» — وعالج السبب بدل إخفائه.
5. فكّر بالوحدات النسبية
استخدام px لكل شيء يجعل التصميم جامداً. اعتمد الوحدات النسبية لتحصل على واجهة تتنفّس مع المستخدم وجهازه:
remللأحجام النصّية والمسافات (تحترم إعدادات حجم الخط لدى المستخدم).%وfrللعرض والشبكات.clamp()لقيمة مرنة بحدّ أدنى وأعلى، مثل:font-size: clamp(1.1rem, 2.5vw, 1.6rem);— حجم يكبر مع الشاشة دون أن يتضخّم أو يصغر أكثر من اللازم.
ومن الملف الإنتاجي مثال على الاستخدام المقتصد: المؤشّر الرئيسي في بطاقة الملخّص هو النصّ الوحيد تقريباً الذي يستعمل font-size: clamp(22px, 6.4vw, 28px)، لأنه العنصر الذي يجب أن يبقى بارزاً على شاشة بعرض 320 بكسل ولا يتضخّم على شاشة عريضة. أما بقية النصوص فأحجامها ثابتة عن قصد لأنها داخل واجهة تطبيق لا صفحة نصّية. القاعدة: clamp() للعناصر التي تحمل التسلسل البصري، لا لكل نصّ في الصفحة.
وفي الوحدات نفسها، لاحظ الفرق بين 100vh و100dvh: نافذة الكتابة في المشروع تستعمل height: 100dvh على الجوال تحديداً، لأنّ vh في متصفّحات الجوال يحسب الشاشة قبل ظهور شريط العنوان أو لوحة المفاتيح فتُقطع النافذة من الأسفل، بينما dvh يتغيّر مع المساحة الفعلية المرئية.
6. صمّم من الأصغر للأكبر (Mobile-first)
ابدأ بأنماط الجوال كأساس، ثم أضِف تحسينات الشاشات الأكبر عبر min-width. هذا المنهج ينتج كوداً أبسط لأنّ الحالة الأساسية هي الأبسط، ويضمن تجربة جيّدة على الأجهزة الأضعف أولاً:
.grid { grid-template-columns: 1fr; }
@media (min-width: 760px) { .grid { grid-template-columns: 1fr 1fr; } }
الواقع في المشاريع الحقيقية أقلّ نقاءً: الملف الذي نقيسه يحتوي 12 استعلام وسائط، بعضها توسيعي بـmin-width مثل @media(min-width:680px){ .health-grid { grid-template-columns: repeat(4,1fr) } }، وبعضها تضييقي بـmax-width لحالات هواتف أصغر: 380 و480 و520 و560 و600 بكسل. نقطة 380 بكسل مثلاً وُجدت لسبب واحد محدّد: شريط تبويبات بستّة أزرار لا يتّسع على الهواتف الضيّقة، فيُصغّر عندها الحشو وحجم الخطّ. الدرس هو أنّ نقاط الانكسار يجب أن تولد من عنصر ينكسر فعلاً، لا من قائمة مقاسات أجهزة تُنسخ من مقال.
الخصائص المنطقية: كيف كلّفتنا «يمين/يسار» 26 قاعدة زائدة
الموقع الذي نتحدّث عنه يعمل بالعربية والإنجليزية معاً، والتبديل بينهما يغيّر سمة dir على عنصر html. هنا يظهر الفرق الحاسم بين نوعين من الخصائص:
- الخاصية الاتجاهية تسمّي جهة ثابتة من الشاشة:
margin-rightوleftوtext-align: right. تبقى كما هي في اللغتين، فلا تعرف شيئاً عن اتجاه القراءة. - الخاصية المنطقية تسمّي موضعاً في اتجاه القراءة:
margin-inline-startيعني «بداية السطر»، فيصير يميناً في العربية ويساراً في الإنجليزية تلقائياً بلا قاعدة إضافية. ومثلهاinset-inline-endوpadding-blockوtext-align: startوtext-align: end.
ثمن تجاهل هذا الفرق مُقاس في ملفنا بدقّة: يحتوي الملف 19 إعلان text-align: right كُتبت أيام كان الموقع عربياً فقط. عند إضافة الإنجليزية لم يكن ممكناً تعديلها كلّها فوراً، فوُلدت كتلة تجاوز كاملة قوامها 26 محدّداً يبدأ كلّ منها بـhtml[dir="ltr"] ومهمّتها الوحيدة إعادة المحاذاة إلى اليسار:
html[dir="ltr"] .auth-form,
html[dir="ltr"] .field-label,
html[dir="ltr"] .install-steps { text-align: left }
لو كُتبت الأصلية text-align: start لما وُجدت هذه الكتلة أبداً: قاعدة واحدة تخدم الاتجاهين بدل قاعدتين ومحدّد أطول وأعلى أولوية. والأسوأ أنّ الكتلة تحتاج صيانة موازية: كل مكوّن جديد يستعمل right يجب أن يُضاف له سطر مقابل في كتلة التجاوز، وإن نُسي ظهر النصّ بمحاذاة خاطئة في الإنجليزية فقط — وهو خطأ لا يراه من يطوّر بالعربية.
في المكوّنات الأحدث استُعملت الخصائص المنطقية فعلاً: inset-inline-start: 0 لشريط اللون على حرف البطاقة، وinset-inline-end: -16px لزر إغلاق نافذة منبثقة، وmargin-inline-end: 6px لفتحة مفتاح اللغة في شريط التنقّل، وtext-align: start وend في صفوف البيانات. لكن الصورة الكاملة أصدق من أي نصيحة مثالية: ستّ خصائص منطقية مقابل 35 استخداماً لـleft/right — أي أنّ الهجرة لم تكتمل. بعض هذه الـ35 مشروع تماماً ولا يُترجم أصلاً، مثل التمركز البصري left: 50% مع transform: translateX(-50%)، فهو موضع في الشاشة لا في النصّ. القاعدة العملية: استعمل المنطقية لكل ما يتبع النصّ، والاتجاهية لما يتبع الشاشة فقط.
7. قلّل التكرار بمبدأ DRY
القاعدة الذهبية: «لا تكرّر نفسك» (Don't Repeat Yourself). إن وجدت مجموعة الخصائص نفسها في عدّة أماكن — حواف دائرية، ظل، حشوة — فاستخرجها في صنف مساعد واحد أو في مكوّن قابل لإعادة الاستخدام. مثال: بدل تكرار display:flex; align-items:center; justify-content:center; في عشرات القواعد، عرّف صنفاً واحداً .center واستعمله. التكرار ليس مجرّد إطالة للملف، بل مصدر أخطاء: تُصلِح موضعاً وتنسى الباقي.
وللمبدأ وجه معاكس يُنسى: القاعدة العامّة الصارمة تحتاج مخرجاً. في مشروعنا تُطبَّق user-select: none على body كله (لأنه يعمل كتطبيق لا كصفحة، والتحديد العابر يشوّه التجربة على اللمس)، لكن ذلك يُلغي قدرة المستخدمة على نسخ نصّها. الحلّ لم يكن التخلّي عن القاعدة بل قائمة استثناء صريحة تعيد التحديد للحقول ومناطق النصّ الحرّة: input, textarea, .m-note-area { user-select: text }. القاعدة العامّة توفّر التكرار، والاستثناء المكتوب في مكان واحد يمنعها من إيذاء المستخدم.
8. استعمل أدوات CSS الحديثة بحكمة
لغة CSS تطوّرت كثيراً، وكثير ممّا كان يتطلّب إطار عمل صار متاحاً أصلاً:
:is()و:where()لتجميع المحدِّدات وتقليل التكرار دون رفع الأولوية.- الخصائص المنطقية مثل
margin-inlineوpadding-block— مهمّة جداً لمواقع RTL العربية لأنها تتكيّف مع اتجاه الكتابة تلقائياً. gapداخلflexوgridلتباعد نظيف دون هوامش مزدوجة.- التداخل (nesting) الأصلي لتجميع قواعد المكوّن الواحد منطقياً.
القاعدة: استعمل الميزة لأنها تبسّط كودك، لا لمجرّد أنها جديدة.
محدّدات حديثة حلّت مشكلة بنيوية لا تجميلية
أفضل ما في المحدّدات الحديثة أنها تحلّ مشاكل كان حلّها سابقاً بالجافاسكربت. المشكلة عندنا كانت فتحات محتوى الشركاء: حاويات تُعرض داخلها أكواد طرف ثالث لا نعرف مقاسها وقت كتابة الأنماط. عرّفنا للحاوية عرضاً أقصى موحّداً --banner-max-w: 720px ليتّسق مقاس الفتحة في الصفحات الثلاث، وهذا صحيح مع محتوى عريض — لكنه ينتج صندوقاً شبه فارغ بعرض 720 بكسل حين يكون المحتوى بنراً بمقاس ثابت 320×50.
الحلّ محدّد واحد يقرأ ما يحتويه العنصر فعلاً بدل تخمين أبعاده:
.partner-block[data-mode="html"]:has(iframe) {
width: fit-content; min-width: min(320px, 100%); }
القراءة بالعربية: إن كانت الحاوية تحتوي إطاراً (أي محتوى بمقاس ثابت) فاجعل عرضها بمقدار محتواها عبر fit-content، مع حدّ أدنى ذكي: min(320px, 100%) يعني «320 بكسل، إلّا إذا كانت الشاشة أضيق فخُذ عرض الشاشة» — فيمنع الانضغاط على الحاسب ويمنع الخروج عن الشاشة على الجوال بقاعدة واحدة. قبل :has() كان هذا يستلزم جافاسكربت يفحص المحتوى بعد تحميله ثم يضيف صنفاً، أي منطق عرض مكتوب في المكان الخطأ.
وفي الطريق وقعنا في فخّ يستحقّ التحذير: قاعدة عامّة تبدو بريئة تماماً هي iframe { max-width: 100%; height: auto }. الشقّ الأول مفيد، لكن height: auto يُلغي سمة height المكتوبة في الإطار فيرتدّ إلى ارتفاعه الافتراضي في المتصفّح وهو 150 بكسل — صندوق فارغ بثلاثة أضعاف ارتفاع البنر الحقيقي (50 بكسل). الإصلاح كان بالحذف لا بالإضافة: max-width: 100% فقط، وترك الارتفاع للسمة. هذا نمط متكرّر في CSS: أكثر ما يكسر التخطيط ليس القاعدة الناقصة بل القاعدة العامّة الزائدة.
ومحدّدان آخران أنجزا ما كان يُكتب بالكود:
.partner-block:empty, .partner-block .partner-block-body:empty { display: none }— الفتحة التي لم يصلها محتوى تختفي بلا مسافة بيضاء غامضة، وبلا سطر جافاسكربت واحد..partner-block:not([data-mode])يعطي سلوكاً افتراضياً معقولاً للسجلّات القديمة المحفوظة في قاعدة البيانات قبل إضافة السمة — تراجع نظيف بدل شرط في الكود يفحص وجود الحقل.
ولمن يعرض محتوى طرف ثالث: القاعدة البصرية وحدها لا تكفي، إذ يحتاج الأمر ضبطاً موازياً في سياسة أمان المحتوى للأطراف الثالثة وإلّا حُجب المحتوى قبل أن يصل إلى الحاوية أصلاً.
9. نظّف ما لا تستعمله
مع الوقت تتراكم أصناف لم تعد مستخدمة في أي صفحة، فتثقل الملف وتربك القارئ. راجِع أنماطك دورياً واحذف الميّت منها. أدوات مثل فحص التغطية (Coverage) في أدوات المطوّر تكشف لك CSS غير المستعمل في الصفحة. ملف أصغر = تحميل أسرع وصيانة أسهل.
قياس الملف: كم يُصغَّر فعلاً، وأين يوضع كل شيء داخله
أرقام التصغير الحقيقية
يُقال دائماً «صغّر ملفاتك» بلا رقم. هذه أرقام من المشروع نفسه بعد تمرير الملفات على أداة تصغير واحدة: ملف الأنماط نزل من 98,183 بايت إلى 85,926 بايت، أي وفّر 12,257 بايت فقط بنسبة 12.5%. في المقابل، ملف الجافاسكربت الرئيسي في المشروع نزل من 393,165 بايت إلى 259,634 بايت بنسبة 34% — أي قرابة ثلاثة أضعاف نسبة الأنماط.
السبب بنيوي لا يتعلّق بجودة الأداة: بنية CSS مكثّفة أصلاً وليس فيها ما يُحذف بأمان. لا أسماء متغيّرات محلية يمكن اختصارها إلى حرف واحد كما في الدوال، ولا كود غير مستدعى يمكن شطبه (المحدّد الذي لا تستعمله اليوم قد يظهر عنصره غداً، فالأداة لا تجرؤ على حذفه)، وأسماء الخصائص والقيم كلها مطلوبة حرفياً. كل ما تفعله الأداة عملياً هو حذف التعليقات والمسافات وتوحيد صيغ الألوان. أما الجافاسكربت فتُعاد تسمية متغيّراته الداخلية، ويُدمج ما يمكن دمجه، ويُحذف ما لا يُستعمل — تفاصيل أوسع في مقال تصغير الكود العربي بـesbuild.
والرقم الأهمّ من التصغير هو الضغط عند النقل: الملف الأصلي مضغوطاً بـgzip يصبح 21,184 بايت (نحو 21 كيلوبايت، أي أقلّ بـ78% من حجمه)، والمصغَّر مضغوطاً يصبح 16,533 بايت. الاستنتاج العملي مزدوج: تأكّد أولاً أنّ الضغط مفعّل على استضافتك لأنه يفعل أكثر من التصغير بأضعاف، ثم صغّر لأنه ما زال يوفّر نحو 4.5 كيلوبايت فوق الضغط ولا سبب للتخلّي عنها. ولا تنسَ الوجه التشغيلي: ملف أنماط يُخدَم بكاش طويل الأمد لن يصل بنسخته الجديدة إلى الزائر بمجرّد رفعه، بل يحتاج ترقية بصمة إصدار في رابطه كما في إدارة إصدارات Service Worker والكاش.
أين تُعرَّف المتغيّرات وأين تُوضع التجاوزات
ترتيب الملف عندنا يتبع ثلاث قواعد صريحة:
- المتغيّرات في أعلى الملف حرفياً: كتلة
:rootتبدأ من السطر الأول وتنتهي عند السطر 14، ثم إعادة الضبط* { margin:0; padding:0; box-sizing:border-box }، ثم أساسياتhtmlوbody. السبب مزدوج: كل قاعدة تالية تستطيع استخدام المتغيّرات، والقادم الجديد إلى الملف يرى نظام التصميم قبل تفاصيله فيفهم بقيّة الملف بسرعة. - استثناء واحد مقصود: المتغيّر
--banner-max-w: 720pxعُرِّف في كتلة:rootثانية بجوار المكوّن الذي يستخدمه، لا في الأعلى. لأنه ليس قراراً في نظام التصميم بل قياس مكوّن واحد؛ إبقاؤه ملاصقاً لمكوّنه يجعل تعديله محلياً ويمنع تضخّم كتلة الجذر بقياسات لا تعني أحداً غير مكوّن بعينه. - التجاوزات بعد ما تتجاوزه دائماً: استعلامات الوسائط الاثنا عشر موضوعة كلٌّ بعد مكوّنه أو في ذيل الملف، وكذلك كتلة تجاوزات الاتجاه الإنجليزي. والسبب ليس ذوقاً تنظيمياً: استعلام الوسائط لا يرفع الأولوية بشيء، وعند تساوي الأولوية يفوز الأخير ترتيباً. فلو وُضع استعلام الجوال قبل القاعدة الأساسية للمكوّن، لأبطلته القاعدة الأساسية بصمت على الجوال، وهو من أكثر الأخطاء إرباكاً لأن الكود يبدو صحيحاً تماماً.
وآخر كتلة في الملف بالكامل هي @media print، وفيها الاستخدام الأنظف لـ!important: تُخفي كل أبناء الصفحة ثم تُظهر عنصر التقرير وحده — body > * { display: none !important } ثم #printReport { display: block !important } مع @page { margin: 14mm }. هذا يحوّل الصفحة إلى تقرير قابل للطبع أو الحفظ كملفّ PDF بلا مكتبة خارجية. والطباعة موضع مشروع لـ!important لأنك تتجاوز فيها أنماط الشاشة كلّها بقاعدتين، وهي بيئة عرض مختلفة لا منافسة داخل الشاشة نفسها.
أسئلة شائعة
هل أحتاج إطار عمل مثل Tailwind أو Bootstrap؟ ليس دائماً. للمشاريع الصغيرة والمتوسّطة، CSS منظّم بمتغيّرات وأصناف واضحة يكفي ويُبقيك متحكّماً. الأطر مفيدة للفِرق الكبيرة والاتساق السريع، لكنها تأتي بحجم وقيود. الملف الذي قِسناه في هذا المقال — 96 كيلوبايت تخدم تطبيقاً كاملاً بستّ سمات لونية ولغتين — مكتوب بلا أي إطار.
متى أستعمل Grid ومتى Flexbox؟ استعمل Flexbox للتوزيع على محور واحد (صف أو عمود)، وGrid للتخطيطات ثنائية الأبعاد (صفوف وأعمدة معاً). غالباً تستعملهما معاً في الصفحة ذاتها.
ما فرق متغيّرات CSS عن متغيّرات Sass؟ متغيّر Sass يُحسم وقت البناء ويختفي من الملف الناتج، فلا يمكن تغييره في المتصفّح — وميزة تخصيص اللون التي شرحناها مستحيلة به. متغيّر CSS يبقى حيّاً وقت التشغيل: يُقرأ ويُكتب بالجافاسكربت، ويُورَّث لكل الأبناء، ويمكن تجاوزه في نطاق محدود (على مكوّن واحد فقط) بلا مساس بالبقيّة.
هل :has() آمن للاستخدام اليوم؟ نعم، صار مدعوماً في المتصفّحات الحديثة كلها. والأهمّ أنّ التراجع في حالتنا غير مؤذٍ: من لا يدعمه يرى الحاوية بعرضها الافتراضي فقط. استعمله لتحسين لا يكسر شيئاً عند غيابه، ولا تبنِ عليه وظيفة أساسية.
كيف أكتشف أنّ لون هوية ثابتاً تسرّب إلى ملفي؟ ابحث نصّياً عن كل ظهور لعلامة # وrgba( في الملف، ثم صنّف كل نتيجة في خانتين: هوية أم دلالة. كل ما في خانة الهوية يجب أن يتحوّل إلى var(). الفحص كلّه لا يستغرق أكثر من نصف ساعة في ملف بحجم مئة كيلوبايت، ويوفّر إعادة تصميم مكوّن كامل لاحقاً.
خلاصة
الكود النظيف ليس رفاهية، بل استثمار في وقتك المستقبلي. سمِّ بوضوح، وحّد القيم في متغيّرات، رتّب الملف، ابتعد عن !important، استعمل الوحدات النسبية، وابدأ من الجوال. وأضِف إليها الدروس الثلاثة التي دفعنا ثمنها في ملف حقيقي: لا تكتب لون هوية بالأرقام أبداً لأنه عيب مؤجّل ينفجر عند أول تخصيص أو سمة داكنة؛ واستعمل الخصائص المنطقية بدل يمين/يسار لأنّ البديل كتلة تجاوز كاملة تحتاج صيانة موازية؛ وضع التجاوزات دائماً بعد ما تتجاوزه لأنّ استعلام الوسائط لا يرفع الأولوية. العادات النظيفة لا تجعل الملف أجمل فحسب، بل تجعل الميزة التي لم تُطلب بعد ممكنةً بتعديل سطر.