EduFlow
إتقان CSS الحديثة
⁦0%⁩

الوحدة 4 · الحركة والمعمارية

معمارية CSS على نطاق واسع

19 دقراءة 4 دبيئة تجريب الكود

الدرس في لمحة

نظّم ملف أنماط ينمو باستخدام طبقات الـ cascade، ومستويات الـ tokens، وcomponents بـ class واحد، ومجموعة صغيرة من الـ utilities، ليستطيع الفريق تغيير الـ CSS دون خوف.

  1. 1كومة الـ layers هي المعمارية
  2. 2الـ tokens على ثلاثة مستويات
  3. 3قواعد الـ components
  4. 4الـ utilities: قليلة وأخيرة
  5. 5تحديد النطاق بـ @scope
  6. 6دورك الآن

لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.

بدأت الواجهة الأمامية لـ Waypoint بمطوّر واحد وملف styles.css واحد. وبعد سنتين صار هناك خمسة مطوّرين، وأربعون component، وموقع مصغّر لأحد الرعاة يغيّر مظهر الجدول، وملف أنماط لا يجرؤ أحد على حذف شيء منه. كل إصلاح خطأ تجاوزٌ جديد، وكل تجاوز أثقل قليلًا من سابقه، وعدد !important لا يفعل إلا أن يزيد.

هذه ليست مشكلة انضباط. إنها مشكلة معمارية، وكل ما في هذه الدورة حتى الآن يعطيك القطع اللازمة لإصلاحها.

كومة الـ layers هي المعمارية

ابدأ ملف الأنماط الرئيسي بتعليمة واحدة تسمّي كل نوع من الـ CSS لديك، بترتيب الأولوية:

main.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 تصل بينها أسهم: قيم لوحة الألوان مثل violet 600 تغذّي tokens دلالية مثل color brand، وهذه تغذّي tokens الـ component مثل card accent. غلاف الراعي يتجاوز card accent. وبجانبها كومة الـ layers من reset إلى utilities. --violet-600 --color-brand --card-accent .theme-sponsor ترتيب الـ layers 1 reset 2 tokens 3 base 4 layouts 5 components 6 utilities اللاحقة تفوز
تتدفّق الـ tokens من القيم الخام إلى الأدوار إلى الـ components، والـ layers تقرّر أيّ نوع من القواعد يفوز

الـ tokens على ثلاثة مستويات

قائمة مسطّحة من 200 custom property صعبة الصيانة بقدر 200 كود hex. وزّعها على مستويات:

  1. لوحة الألوان: قيم خام، مسمّاة بحسب ماهيتها. --violet-600: oklch(52% 0.22 290);
  2. الدلالي: أدوار، مسمّاة بحسب الغرض منها. --color-brand: var(--violet-600); --surface: light-dark(…);
  3. الـ component: مقابض الـ component العامة، وقيمها الافتراضية tokens دلالية. --card-accent: var(--color-brand);

أبقِ المستوى الأول صغيرًا ومملًّا: نحو اثني عشر لونًا (hue) لكلٍّ منها بضع درجات تكفي معظم المنتجات. القرارات المثيرة تحدث في المستوى الثاني، حيث يتّفق المصمّم والمطوّر على ما تعنيه «brand» أو «surface» أو «danger» في كل ثيم.

الـ components لا تقرأ إلا المستويين الثاني والثالث. عندها تغيّر إعادة تصميم الهوية المستوى الأول، ويغيّر الوضع الداكن المستوى الثاني، وتغيّر صفحة الراعي المستوى الثالث على غلاف:

CSS
@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 القواعد في شجرة فرعية وتستطيع التوقّف عند حدّ داخلي:

CSS
@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.

قراءات إضافية

تحقّق من فهمك

3 أسئلة

السؤال 1 من 3

صفحة الراعي تحتاج إلى أن يكون لون التمييز في كل بطاقة جلسة أزرق مخضرًا (teal) بدل البنفسجي. في معمارية مبنية على الـ tokens، ما التغيير الصحيح؟

السؤال 2 من 3

لماذا لا ينبغي أن تضبط .session-card قيمة margin-bottom: 16px على نفسها؟

السؤال 3 من 3

أين يجب أن تعيش الـ utility .u-hidden { display: none; } لتتغلّب دائمًا على قواعد display في الـ components دون !important؟

تدريب

بيئة تجريب الكود

أعد هيكلة البطاقة في معمارية

أعد هيكلة هذه الشريحة من ملف الأنماط القديم. أبقِ الـ HTML كما هو.

  1. أعلن layers تتضمّن reset وtokens وcomponents وutilities بهذا الترتيب (يمكنك إضافة layouts أو غيرها بينها)، وضع كل قاعدة في layer.
  2. لا selectors بـ ID ولا !important في أيّ مكان.
  3. حدّ التمييز في البطاقة يقرأ token اسمه --card-accent قيمته الافتراضية #6d28d9؛ و.theme-sponsor تضبطه على #0e7490.
  4. ما زالت .u-hidden تخفي شارة Draft.
  5. البطاقات بلا margins خاصة بها؛ وتخطيط .schedule يضع 16px بينها.
<main id="schedule" class="schedule">
  <div class="theme-sponsor">
    <article class="session-card" id="sponsored">
      <h3>Sponsor Talk: Shipping at Scale</h3>
      <span class="badge u-hidden">Draft</span>
    </article>
  </div>
  <article class="session-card" id="regular">
    <h3>CSS Architecture at Scale</h3>
    <span class="badge">Platform</span>
  </article>
</main>جارٍ تحميل المحرّر…

Ctrl/⌘ + Enter للتشغيل · يعمل في بيئة معزولة داخل متصفّحك. لا شيء يغادر جهازك.

المعاينة

الـ console

(لا مخرجات)

الفحوص

  • الـ layers reset وtokens وcomponents وutilities مُعلنة بالترتيب
  • لا selectors بـ ID ولا !important
  • البطاقات العادية تستخدم token --card-accent البنفسجي
  • بطاقات الراعي زرقاء مخضرّة عبر الـ token
  • تغيير --card-accent يغيّر ثيم البطاقة
  • الـ utility u-hidden ما زالت تخفي شارة Draft
  • البطاقات بلا margins؛ والتخطيط يباعد بينها 16px