في السابق، كانت أدوات اختيار لغة CSS قادرة على التنقّل للأسفل فقط، أي أنّه كان بإمكانك تصميم عنصر فرعي استنادًا إلى العنصر الرئيسي، ولكن ليس العكس. تغيّر الفئة الزائفة :has() ذلك، ما يتيح لك تصميم عنصر حاوية بشكل مشروط استنادًا إلى توفّر العناصر التابعة له أو حالتها.
من خلال الجمع بين :has() وفئات الحالة الزائفة (مثل :checked أو :focus أو :valid أو :invalid أو :not())، يمكنك إنشاء مكوّنات واجهة مستخدم تفاعلية ومعقّدة بالكامل في CSS، بدون الحاجة إلى JavaScript لتبديل فئات "المعدِّل" (مثل .is-active أو .has-error) في العناصر الرئيسية.
ويُعدّ ذلك مفيدًا بشكل خاص للمكوّنات التي تحتاج إلى الاستجابة للتفاعلات الداخلية، مثل زر تبديل المظهر المترجَم الذي يتفاعل مع مربّع اختيار (:checked)، أو مجموعة نماذج تبرز خطأ (:invalid)، أو بطاقة ترتفع عند التركيز على رابط فرعي (:focus-within).
تنفيذ نمط الحاوية المستند إلى الحالة
إلزامي: يجب استخدام أداة الاختيار :has() في عنصر الحاوية لرصد الحالة المحدّدة (مثل :checked أو :focus أو :invalid) لعنصرها الثانوي التفاعلي.
لإنشاء مكوّن يغيّر نمطه استنادًا إلى حالة مكوّن فرعي:
- تحديد النمط التلقائي: اضبط متغيّرات CSS على الحاوية لتحديد حالتها الأساسية.
- تطبيق عمليات إلغاء مستندة إلى الحالة: استهدِف الحاوية باستخدام
:has([child-selector]:[state])وأعِد تحديد متغيّرات CSS للحالة النشطة أو البديلة.
مثال: مكوّن يغيّر المظهر استنادًا إلى زر تبديل فرعي
/* 1. Define the default state on the component container */
.theme-card {
/* Using custom properties makes state-switching cleaner */
--card-bg: #ffffff;
--card-text: #333333;
--card-border: #cccccc;
background-color: var(--card-bg);
color: var(--card-text);
border: 1px solid var(--card-border);
/* Use a transition for smooth state changes */
transition: background-color 0.3s, color 0.3s;
}
/* 2. Apply styles when the child enters the specific state */
/* MANDATORY: Target the container and use :has() to check the descendant's state */
.theme-card:has(.theme-toggle:checked) {
/* Override the properties for the active or alternate state */
--card-bg: #222222;
--card-text: #f0f0f0;
--card-border: #555555;
}
/* You can also combine :has() and :not() to target specific negative states */
/* This selects a card that DOES NOT have a toggle in the checked state */
.theme-card:not(:has(.theme-toggle:checked)) {
/* Optional: Explicit styles for the unchecked state if needed */
}
<!-- The container element that receives the styling -->
<div class="theme-card">
<!-- The child element whose state controls the parent -->
<label>
<input type="checkbox" class="theme-toggle">
Enable Dark Mode
</label>
<h2>Card Title</h2>
<p>The style of this entire card is controlled by the checkbox above.</p>
</div>
نصيحة بشأن الأداء: عند استخدام :has()، حدِّد نطاق أداة الاختيار على الحاوية الأكثر تحديدًا (مثل .theme-card). تجنَّب ربطها بعناصر عالية المستوى مثل body:has(...) إذا كانت تغييرات التصميم محلية، لأنّ طلبات البحث الواسعة النطاق في :has() يمكن أن تؤدي إلى إعادة احتساب التنسيق بشكل أكبر.
استراتيجيات احتياطية
إذا كان التصميم المستند إلى الحالة مهمًا لتجربة المستخدم أو لتنسيق الصفحة، يجب توفير بديل للمتصفحات التي لا تتيح استخدام أداة الاختيار :has(). بالنسبة إلى التأثيرات الزخرفية البحتة، يمكن استخدام :has() كتحسين تدريجي بدون عنصر احتياطي.
إلزامي: عند تنفيذ إجراء احتياطي للميزات المهمة، يجب استخدام @supports not selector(:has(*)) في CSS لتحديد إجراء احتياطي تقليدي يستند إلى الفئات. إذا كان تغيير الحالة المهم يعتمد على تفاعل المستخدم، يجب أيضًا استخدام نص برمجي صغير مضمّن مع CSS.supports() لتبديل هذه الفئة استنادًا إلى حدث JavaScript المكافئ (مثل change أو focus أو blur) الذي يمثّل تغيير الحالة.
/* Fallback CSS for older browsers */
/* We check if the browser DOES NOT support the :has() selector */
@supports not selector(:has(*)) {
/* Define a traditional modifier class that applies the exact same overrides */
.theme-card.is-active {
--card-bg: #222222;
--card-text: #f0f0f0;
--card-border: #555555;
}
}
/* Fallback JavaScript for older browsers */
/* Check for support before running the script to avoid unnecessary work in modern browsers */
if (!CSS.supports('selector(:has(*))')) {
const toggle = document.querySelector('.theme-toggle');
const card = document.querySelector('.theme-card');
if (toggle && card) {
// Manually toggle the fallback class when the input state changes
toggle.addEventListener('change', (e) => {
card.classList.toggle('is-active', e.target.checked);
});
}
}