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

الوحدة 1 · الـ Cascade كما هو فعلًا

الـ Nesting الأصلي و:has()

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

الدرس في لمحة

اكتب أنماط الـ components بالـ nesting الأصلي في CSS، واستخدم :has() لتنسيق عنصر بحسب ما يحتويه، بما في ذلك فلتر للجدول لا يحتاج إلى JavaScript.

  1. 1الـ nesting الأصلي
  2. 2:has(): نسّق العنصر بحسب ما يحتويه
  3. 3شريط فلاتر بلا JavaScript
  4. 4ثلاثة selectors أخرى تحلّ محلّ JavaScript
  5. 5دورك الآن

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

أمران يزعجان كل من يصون CSS البطاقات في Waypoint. قواعد البطاقة مبعثرة: القاعدة الأساسية هنا، وحالة المرور بعدها بعشرين سطرًا، وتجاوز الوضع الداكن في ملف آخر. وفريق التصميم لا يتوقّف عن طلب أنماط تعتمد على ما داخل البطاقة («إن كانت الجلسة ممتلئة، اجعلها باهتة»)، وهذا كان يعني سابقًا تبديل class بـ JavaScript.

الـ nesting الأصلي (التداخل) يحلّ الأمر الأول، و:has() تحلّ الثاني.

الـ nesting الأصلي

تستطيع الآن أن تضع القواعد داخل القواعد، في CSS عادية، دون أيّ خطوة بناء:

session-card.css
.session-card {
  padding: 1rem;
  border: 1px solid #e5e7eb;
  border-radius: 12px;

  h2 {
    margin-block: 0 0.5rem;
    font-size: 1.125rem;
  }

  &:hover {
    border-color: #a78bfa;
  }

  .featured & {
    border-width: 2px;
  }

  @media (width >= 48rem) {
    padding: 1.5rem;
  }
}

& تمثّل الـ selector الأب. &:hover تعني «هذه البطاقة حين يمرّ المؤشّر فوقها»، و.featured & تعني «هذه البطاقة داخل .featured». والـ selector المتداخل بلا &، مثل h2، يُعامَل كعنصر داخلي: .session-card h2. وقواعد @media و@container المتداخلة تنطبق على الـ selector الأب، فيعيش تغيير الـ padding بجوار الـ padding نفسه.

يمكن أن تبدأ الـ selectors المتداخلة باسم عنصر (h2 لا & h2). كانت التطبيقات الأولى تشترط أن يأتي رمز أولًا؛ أما الآن فكل المتصفّحات الحالية تدعم الصيغة المرنة، والـ nesting ضمن Baseline منذ ديسمبر 2023 (ومتاح على نطاق واسع منذ منتصف 2026).

قاعدتان أخريان تحافظان على صحّة الـ nesting. في العمق، تتصرّف & كأنها :is() ملفوفة حول قائمة الأب، لذا فإن #keynote, .session-card { h2 { … } } تعطي كل قاعدة h2 متداخلة specificity بمستوى ID. وتوقّف عند مستويين من العمق: الـ selector الذي لا تستطيع قراءته بنظرة واحدة لن تستطيع تجاوزه بنظرة واحدة.

:has(): نسّق العنصر بحسب ما يحتويه

تأخذ :has() selector نسبيًا وتطابق العنصر إن وجد ذلك الـ selector شيئًا:

CSS
/* A card that contains a speaker photo gets a two-column layout */
.session-card:has(img) {
  display: grid;
  grid-template-columns: 64px 1fr;
  gap: 1rem;
}

/* Dim sold-out sessions */
.session-card:has(.badge-full) {
  opacity: 0.6;
}

/* A label whose following input is invalid after the user typed */
label:has(+ input:user-invalid) {
  color: #b91c1c;
}

المثال الأخير يُظهر أن :has() أكثر من مجرد «selector للعنصر الأب»: + input تنظر إلى الشقيق التالي، فيستطيع عنصر سابق أن يتفاعل مع عنصر لاحق. و:user-invalid هي القريبة المهذّبة لـ :invalid؛ فهي لا تطابق إلا بعد أن يتفاعل المستخدم مع الحقل، فلا يُحمَّل نموذج فارغ مغطّى بالأحمر.

لـ :has() حدّان يستحقّان المعرفة قبل أن تصطدم بهما. لا يمكنك وضع :has() داخل :has() أخرى، ولا وضع pseudo-elements مثل ::before بداخلها. والـ specificity الخاصة بها تتبع قاعدة :is() التي رأيناها سابقًا في هذا القسم: أدقّ وسيط هو الذي يُحسب، لذا فإن .session-card:has(#keynote-badge) تحمل وزن ID.

شريط فلاتر بلا JavaScript

هنا تغيّر :has() طريقة بنائك للأشياء. شريط الفلاتر في Waypoint مجموعة من مربّعات الاختيار. ولأن مربّعات الاختيار والبطاقات تشترك في سلف واحد هو .schedule، تستطيع حالة مربّع الاختيار أن تقود البطاقات:

HTML
<section class="schedule">
  <div class="filters">
    <label><input type="checkbox" id="hide-full"> Hide sold-out</label>
  </div>
  <article class="session-card">…<span class="badge-full">Sold out</span></article>
  <article class="session-card">…</article>
</section>
CSS
.schedule:has(#hide-full:checked) .session-card:has(.badge-full) {
  display: none;
}

اقرأها من اليمين إلى اليسار: أخفِ البطاقة التي تحتوي على شارة «ممتلئة»، حين تكون داخل جدول يحتوي على مربّع #hide-full مُفعَّل. ويعيد المتصفّح تقييمها لحظة تغيّر مربّع الاختيار.

ثلاثة selectors أخرى تحلّ محلّ JavaScript

تحظى :has() بالاهتمام، لكن بضعة selectors أهدأ تستحق مكانها في صفحة Waypoint أيضًا:

CSS
/* Highlight the whole filter bar while any control inside it has focus */
.filters:focus-within { outline: 2px solid #a78bfa; }

/* Every card except sold-out and cancelled ones */
.session-card:not(.is-full, .is-cancelled) { cursor: pointer; }

/* Stripe only the visible cards, skipping hidden ones */
.session-card:nth-child(even of :not([hidden])) { background: #f9fafb; }

:focus-within تطابق العنصر حين يكون عليه أو على أيّ شيء بداخله الـ focus. و:not() تقبل قائمة كاملة، فلم تعد تحتاج إلى سلسلة :not(.a):not(.b). وصيغة of S في :nth-child() لا تعدّ إلا الأشقّاء المطابقين لـ S، وهذا يصلح التلوين المتناوب للصفوف الذي ينكسر لحظة يخفي الفلتر أحدها. والثلاثة تعمل في كل المتصفّحات الحالية.

دورك الآن

في التمرين ستعيد كتابة أنماط البطاقة بالـ nesting، ثم تضيف قاعدتين بـ :has(): واحدة تخفي الجلسات الممتلئة حين يُفعَّل مربّع الفلتر، وأخرى تُبرز البطاقة حين يُفعَّل مربّع «الحفظ» فيها. وبهذا يكتمل قسم الـ cascade؛ وبعده ستنتقل من أيّ الأنماط ينطبق إلى أين تذهب الأشياء، بدءًا بـ flexbox.

أهم النقاط

  • الـ nesting الأصلي يجمع حالات الـ component وأبناءه والـ media queries الخاصة به داخل قاعدته، و& تمثّل الـ selector الأب.
  • الـ selector المتداخل يأخذ specificity :is() مطبّقةً على قائمة الأب، لذا إن احتوت قائمة الأب على ID أصبحت كل قاعدة متداخلة ثقيلة بوزن ID.
  • الـ nesting في CSS لا يلصق السلاسل النصية كما يفعل Sass، لذا لن تُنتج &__title أبدًا .card__title.
  • :has() تطابق العنصر حين يطابق الـ selector النسبي داخلها، ما يتيح لعنصر أب، أو لعنصر شقيق سابق، أن يتفاعل مع أبنائه أو مع أشقّائه اللاحقين.

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

تحقّق من فهمك

4 أسئلة

السؤال 1 من 4

ما الـ selector الذي تُنتجه هذه القاعدة المتداخلة؟

CSS
.session-card {
  .speaker & { padding: 0; }
}
السؤال 2 من 4

أيّ selector ينسّق بطاقة الجلسة فقط حين تحتوي على صورة؟

السؤال 3 من 4

أيّ قاعدة تخفي كل بطاقة ليست من مسار design حين يُفعَّل مربّع الاختيار #only-design الموجود داخل .schedule؟

السؤال 4 من 4

كتبت .card { &__title { font-weight: 700; } } بحكم العادة من Sass. ماذا يحدث؟

تدريب

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

اجمع أنماط البطاقة بالـ nesting وصفِّ الجلسات بـ :has()

ثلاث مهام، كلها في CSS:

  1. أعد كتابة قواعد .session-card الثلاث (الأساسية وh2 و:hover) على شكل قاعدة متداخلة واحدة على .session-card.
  2. حين يُفعَّل مربّع الاختيار #hide-full، أخفِ (display: none) كل .session-card تحتوي على .badge-full.
  3. حين يُفعَّل مربّع .save في بطاقة ما، أعطِ تلك البطاقة لون حدّ #6d28d9.

لا تضف JavaScript ولا تغيّر الـ HTML.

<section class="schedule">
  <div class="filters">
    <label><input type="checkbox" id="hide-full"> Hide sold-out sessions</label>
  </div>
  <article class="session-card" id="c1">
    <h2>Shipping View Transitions</h2>
    <label class="save-label"><input type="checkbox" class="save"> Save</label>
  </article>
  <article class="session-card" id="c2">
    <h2>Grid Layout Workshop</h2>
    <span class="badge-full">Sold out</span>
    <label class="save-label"><input type="checkbox" class="save"> Save</label>
  </article>
  <article class="session-card" id="c3">
    <h2>Color Spaces for Humans</h2>
    <label class="save-label"><input type="checkbox" class="save"> Save</label>
  </article>
</section>جارٍ تحميل المحرّر…

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

المعاينة

الـ console

(لا مخرجات)

الفحوص

  • أنماط البطاقة تستخدم الـ nesting الأصلي
  • كل البطاقات ظاهرة قبل التصفية
  • تفعيل الفلتر يخفي البطاقة الممتلئة وحدها
  • إلغاء تفعيل الفلتر يُظهرها من جديد
  • البطاقة المحفوظة تأخذ حدًّا بنفسجيًا