الوحدة 4 · الحركة والمعمارية
معمارية CSS على نطاق واسع
19 دقراءة 4 دبيئة تجريب الكود
الدرس في لمحة
نظّم ملف أنماط ينمو باستخدام طبقات الـ cascade، ومستويات الـ tokens، وcomponents بـ class واحد، ومجموعة صغيرة من الـ utilities، ليستطيع الفريق تغيير الـ CSS دون خوف.
- 1كومة الـ layers هي المعمارية
- 2الـ tokens على ثلاثة مستويات
- 3قواعد الـ components
- 4الـ utilities: قليلة وأخيرة
- 5تحديد النطاق بـ @scope
- 6دورك الآن
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
بدأت الواجهة الأمامية لـ Waypoint بمطوّر واحد وملف styles.css واحد. وبعد سنتين صار هناك خمسة مطوّرين، وأربعون component، وموقع مصغّر لأحد الرعاة يغيّر مظهر الجدول، وملف أنماط لا يجرؤ أحد على حذف شيء منه. كل إصلاح خطأ تجاوزٌ جديد، وكل تجاوز أثقل قليلًا من سابقه، وعدد !important لا يفعل إلا أن يزيد.
هذه ليست مشكلة انضباط. إنها مشكلة معمارية، وكل ما في هذه الدورة حتى الآن يعطيك القطع اللازمة لإصلاحها.
كومة الـ layers هي المعمارية
ابدأ ملف الأنماط الرئيسي بتعليمة واحدة تسمّي كل نوع من الـ CSS لديك، بترتيب الأولوية:
@layer reset, tokens, base, layouts, components, utilities;
@import url("reset.css") layer(reset);
@import url("tokens.css") layer(tokens);
@import url("base.css") layer(base);
@import url("layouts/schedule.css") layer(layouts);
@import url("components/session-card.css") layer(components);
@import url("components/speaker.css") layer(components);
@import url("utilities.css") layer(utilities);لكل layer مهمة واحدة:
| الـ layer | تحتوي على | مثال |
|---|---|---|
| reset | توحيد افتراضيات المتصفّح | *, *::before { box-sizing: border-box; } |
| tokens | custom properties فقط | --color-brand، --space-m |
| base | أنماط العناصر المجرّدة، بـ specificity منخفضة | :where(a) { color: var(--link); } |
| layouts | بنية الصفحة ومناطقها | .schedule { display: grid; gap: … } |
| components | كتلة واحدة لكل component | .session-card { … } |
| utilities | تجاوزات أحادية الغرض | .u-hidden، .u-visually-hidden |
لأن الـ layers تحسم التعارض بين الأنواع، تستطيع الـ selectors داخل كل layer أن تبقى مسطّحة: class واحد للـ component، و:where() للأنماط الأساسية. لا أحد يحتاج إلى ID أو سلسلة من ثلاثة classes ليفوز، فلا أحد يكتبها.
الـ tokens على ثلاثة مستويات
قائمة مسطّحة من 200 custom property صعبة الصيانة بقدر 200 كود hex. وزّعها على مستويات:
- لوحة الألوان: قيم خام، مسمّاة بحسب ماهيتها.
--violet-600: oklch(52% 0.22 290); - الدلالي: أدوار، مسمّاة بحسب الغرض منها.
--color-brand: var(--violet-600); --surface: light-dark(…); - الـ component: مقابض الـ component العامة، وقيمها الافتراضية tokens دلالية.
--card-accent: var(--color-brand);
أبقِ المستوى الأول صغيرًا ومملًّا: نحو اثني عشر لونًا (hue) لكلٍّ منها بضع درجات تكفي معظم المنتجات. القرارات المثيرة تحدث في المستوى الثاني، حيث يتّفق المصمّم والمطوّر على ما تعنيه «brand» أو «surface» أو «danger» في كل ثيم.
الـ components لا تقرأ إلا المستويين الثاني والثالث. عندها تغيّر إعادة تصميم الهوية المستوى الأول، ويغيّر الوضع الداكن المستوى الثاني، وتغيّر صفحة الراعي المستوى الثالث على غلاف:
@layer tokens {
:root {
--color-brand: oklch(52% 0.22 290);
--card-accent: var(--color-brand);
}
.theme-sponsor {
--card-accent: oklch(55% 0.11 210);
}
}كل بطاقة داخل .theme-sponsor تصبح زرقاء مخضرّة: الحدّ والشارة وحالة المرور، كل شيء، لأنها كلها تقرأ --card-accent.
قواعد الـ components
حفنة من الأعراف تُبقي أربعين component قابلة للتوقّع:
- class جذري واحد، وأجزاؤه متداخلة بداخله:
.session-card { h3 { … } .badge { … } }. ويبقى الـ nesting بعمق مستوى أو مستويين. - الحالة عبر السمات:
[aria-pressed="true"]و[data-state="live"]و:has(:checked). السمة هي أيضًا الحالة التي تقرؤها التقنيات المساعدة، فلا يمكن أن تختلف CSS عنها. - لا margins خارجية على الجذر. الـ component لا يعرف أين يعيش.
الـ utilities: قليلة وأخيرة
الـ utilities classes أحادية الغرض يستطيع الـ HTML تطبيقها مباشرةً: .u-hidden و.u-visually-hidden و.u-flow (الإيقاع الرأسي). ضعها في آخر layer وستفوز على أيّ component دون !important. أبقِ المجموعة صغيرة ومملّة. إن وجدت نفسك تكتب .u-mt-17، فأنت تبني لغة تنسيق ثانية داخل الأولى؛ إطار utilities قائم على الـ design tokens خيار مشروع، لكن اجعله قرارًا مقصودًا، لا انجرافًا.
تحديد النطاق بـ @scope
حين يجب ألّا يُسرّب component أنماطه إلى محتوى متداخل، مثل ملخّص يعرضه الـ CMS داخل بطاقة، تحصر @scope القواعد في شجرة فرعية وتستطيع التوقّف عند حدّ داخلي:
@scope (.session-card) to (.cms-content) {
p { margin-block: 0.5rem; }
}قاعدة الفقرة تنطبق داخل البطاقات لا داخل .cms-content. أصبحت @scope ضمن Baseline في مارس 2026، فهي أداة حديثة؛ والـ layers والـ components ذات الـ class الواحد تحلّ معظم مشكلات النطاق أصلًا، و@scope تتولّى حالات «الكعكة المثقوبة» (donut) المتبقية.
دورك الآن
التمرين شريحة صغيرة من ملف الأنماط القديم: selectors بـ ID، و!important، وcomponent له margin خاصة به. أعد هيكلته في layers مع token للون تمييز البطاقة، وثيم للراعي، ومسافات يملكها التخطيط، وutility تفوز بنظافة. والدرس الأخير ينظر في كلفة كل هذه الـ CSS على المتصفّح، وفي كيفية إبقاء الحركات والتخطيطات سريعة.
أهم النقاط
- ترتيب layers مثل
reset, tokens, base, layouts, components, utilitiesيحسم التعارض بين أنواع الـ CSS، فتستطيع الـ selectors أن تبقى بـ class واحد. - الـ tokens تعمل أفضل على مستويات: قيم لوحة الألوان الخام، وأدوار دلالية مثل
--color-brand، وخصائص على مستوى الـ component مثل--card-accentيستطيع العنصر الأب تجاوزها. - لا ينبغي للـ components أن تضبط الـ margins الخارجية الخاصة بها؛ التخطيط المحيط بها هو صاحب المسافات، وعادةً عبر
gap. - أبقِ الـ utilities قليلة وأحادية الغرض، في الـ layer الأخيرة، لتفوز دون
!important.