المحددات الحديثة: :is() و :where() و :has() و :not()
تطور محددات CSS
مرت محددات CSS بتطور ملحوظ. منحنا CSS المبكر محددات أساسية مثل محدد العنصر والفئة والمعرف والمحدد التنازلي. مع مرور الوقت، حصلنا على محددات السمات والفئات الزائفة مثل :hover و:first-child والمجمعات مثل محدد الأخ المجاور. لكن كتابة أنماط محددات معقدة كانت لا تزال تتطلب كودا مطولا ومتكررا. إذا أردت تنسيق العناوين داخل المقالات والأقسام والجوانب، كان عليك كتابة كل تركيبة بشكل منفصل مما ينشئ قوائم محددات طويلة يصعب صيانتها.
يقدم CSS الحديث أربعة محددات فئات زائفة قوية تغير جوهريا طريقة كتابة منطق المحددات: :not() لاستبعاد العناصر و:is() لمطابقة أي من عدة محددات بخصوصية عادية و:where() للمطابقة بخصوصية صفرية و:has() لتحديد العناصر الأب بناء على أبنائها. هذه المحددات تقلل التكرار وتحسن قابلية القراءة وتمنحك تحكما دقيقا في الخصوصية -- وفي حالة :has() -- تفتح بعدا جديدا تماما من التنسيق كان مستحيلا سابقا في CSS. بعد هذا الدرس ستكتب ملفات تنسيق أنظف وأسهل في الصيانة مع سطور كود أقل بكثير.
الفئة الزائفة :not()
تحدد الفئة الزائفة :not() العناصر التي لا تطابق وسيط المحدد المعطى. كانت متاحة منذ CSS3 لكن إمكانياتها كانت محدودة أصلا بقبول محدد بسيط واحد فقط. يوسع CSS الحديث :not() بشكل كبير لقبول محددات معقدة ووسائط متعددة مفصولة بفواصل مما يجعلها أقوى بكثير. فكر في :not() كمرشح يزيل العناصر المطابقة من التحديد -- كل شيء يمر عبر المرشح يتلقى التنسيقات.
الاستخدام الأساسي لـ :not()
في أبسط أشكالها تأخذ :not() محددا واحدا وتستبعد العناصر المطابقة. هذا مفيد لتطبيق التنسيقات على جميع عناصر نوع معين باستثناء تلك التي لديها فئة أو سمة أو حالة محددة.
محددات :not() الأساسية
/* تنسيق جميع الفقرات عدا التي لديها فئة "intro" */
p:not(.intro) {
text-indent: 1.5em;
}
/* تنسيق جميع الروابط عدا المحوم عليها */
a:not(:hover) {
text-decoration: none;
}
/* تنسيق جميع المدخلات عدا المعطلة */
input:not(:disabled) {
border-color: var(--border-light, #ccc);
}
/* تنسيق جميع عناصر القائمة عدا الأخير */
li:not(:last-child) {
margin-bottom: 0.5rem;
border-bottom: 1px solid var(--border-light, #eee);
}
/* تنسيق جميع الصور بدون نص بديل */
img:not([alt]) {
outline: 3px solid red; /* تصحيح إمكانية الوصول */
}
/* تنسيق جميع العناصر عدا الابن الأول */
.stack > *:not(:first-child) {
margin-top: 1rem;
}
:not() مع وسائط متعددة
تقبل :not() الحديثة قائمة محددات مفصولة بفواصل. يتم استبعاد العنصر إذا طابق أيا من المحددات المدرجة. هذا مكافئ لسلسلة عدة فئات زائفة :not() لكنه أكثر إيجازا وقابلية للقراءة. على سبيل المثال :not(.a, .b) هو نفس :not(.a):not(.b) -- كلاهما يستبعد العناصر ذات الفئة "a" أو الفئة "b".
:not() مع محددات متعددة
/* استبعاد فئات متعددة مرة واحدة */
.nav-link:not(.active, .disabled) {
opacity: 0.7;
}
/* تنسيق جميع عناصر النموذج عدا الأزرار وأزرار الإرسال */
.form-group :not(button, [type="submit"], [type="reset"]) {
display: block;
width: 100%;
}
/* جميع العناوين عدا h1 و h2 */
:is(h1, h2, h3, h4, h5, h6):not(h1, h2) {
font-weight: 500;
}
/* استبعاد حالات متعددة */
.button:not(:disabled, .loading, [aria-busy="true"]) {
cursor: pointer;
}
/* جميع الأبناء عدا الأول والأخير */
.list > *:not(:first-child, :last-child) {
border-top: 1px solid var(--border-light, #eee);
border-bottom: 1px solid var(--border-light, #eee);
}
:not() مع محددات معقدة
تسمح المتصفحات الحديثة لـ :not() بقبول محددات معقدة بما في ذلك المحددات ذات المجمعات. هذا يعني أنه يمكنك استبعاد العناصر بناء على سياقها -- عنصرها الأب أو موقعها في التسلسل الهرمي أو علاقتها بعناصر أخرى.
محددات معقدة داخل :not()
/* الفقرات التي ليست داخل مقال */
p:not(article p) {
max-width: 65ch;
}
/* الروابط التي ليست داخل التنقل */
a:not(nav a) {
color: var(--primary, #0066cc);
text-decoration: underline;
}
/* المدخلات التي ليست داخل .form-group */
input:not(.form-group input) {
margin-bottom: 1rem;
}
/* الصور التي ليست داخل عنصر figure */
img:not(figure img) {
border-radius: 0.5rem;
}
/* الـ divs التي ليست أبناء مباشرين لـ main */
div:not(main > div) {
padding: 0;
}
:not() بواسطة وسيطها الأكثر خصوصية. :not(.foo) لها خصوصية محدد فئة واحد (0, 1, 0). :not(#bar) لها خصوصية محدد معرف (1, 0, 0). عند استخدام وسائط متعددة مثل :not(.a, #b) تكون الخصوصية هي خصوصية الوسيط الأكثر تحديدا -- في هذه الحالة محدد المعرف (1, 0, 0). الفئة الزائفة :not() نفسها لا تضيف إلى الخصوصية.الفئة الزائفة :is()
تأخذ الفئة الزائفة :is() قائمة محددات مفصولة بفواصل وتطابق أي عنصر يطابق واحدا على الأقل من المحددات في القائمة. هدفها الأساسي هو تقليل التكرار في ملفات التنسيق. قبل :is() كان تنسيق نفس الخاصية على عناصر في سياقات مختلفة يتطلب كتابة كل تركيبة بشكل فردي مما ينتج غالبا قوائم محددات طويلة يصعب التعامل معها. مع :is() يمكنك تجميع الأجزاء المتغيرة من المحدد في تعبير واحد قابل للقراءة.
تقليل تكرار المحددات
فكر في سيناريو شائع: تريد تنسيق جميع العناوين داخل المقالات والأقسام والجوانب. بدون :is() ستكتب محددا منفصلا لكل تركيبة. مع :is() تعبر عن التجميع في السطر مما يجعل النية واضحة على الفور.
قبل وبعد :is()
/* بدون :is() -- مطول ومتكرر */
article h2,
article h3,
article h4,
section h2,
section h3,
section h4,
aside h2,
aside h3,
aside h4 {
color: var(--text-dark, #1a1a1a);
line-height: 1.3;
}
/* مع :is() -- نظيف وقابل للقراءة */
:is(article, section, aside) :is(h2, h3, h4) {
color: var(--text-dark, #1a1a1a);
line-height: 1.3;
}
/* مثال آخر: تنسيق الروابط في حاويات متعددة */
/* بدون :is() */
.header a:hover,
.footer a:hover,
.sidebar a:hover {
text-decoration: underline;
}
/* مع :is() */
:is(.header, .footer, .sidebar) a:hover {
text-decoration: underline;
}
لاحظ كيف انهارت تسعة محددات في سطر واحد. تعمل الفئة الزائفة :is() كآلية تجميع في أي موقع في المحدد. يمكنك استخدامها في البداية أو في المنتصف أو في النهاية من سلسلة المحدد. كل :is() تتوسع إلى جميع وسائطها عندما يطابق المتصفح العناصر، لذا :is(article, section) :is(h2, h3) مكافئة لأربعة محددات منفصلة.
استخدام :is() في مواقع مختلفة
الفئة الزائفة :is() ليست مقتصرة على بداية المحدد. يمكنك وضعها في أي مكان يكون فيه محدد بسيط -- في النهاية لتجميع العناصر المستهدفة أو في المنتصف لتجميع الأسلاف أو حتى عدة مرات في نفس المحدد.
:is() في مواقع مختلفة
/* في البداية: تجميع سياقات الأب */
:is(.card, .panel, .modal) .title {
font-size: 1.25rem;
font-weight: 600;
}
/* في النهاية: تجميع العناصر المستهدفة */
.form-group :is(input, select, textarea) {
border: 1px solid var(--border-light, #ccc);
border-radius: 0.375rem;
padding: 0.5rem 0.75rem;
}
/* في المنتصف: تجميع العناصر الوسيطة */
main :is(article, section) p {
line-height: 1.7;
max-width: 65ch;
}
/* عدة :is() في محدد واحد */
:is(header, footer) :is(nav, .nav) :is(a, button) {
display: inline-flex;
align-items: center;
padding: 0.5rem 1rem;
}
/* تجميع الفئات الزائفة */
.button:is(:hover, :focus-visible) {
background-color: var(--primary-light, #0077ee);
outline: none;
}
/* تجميع محددات السمات */
input:is([type="text"], [type="email"], [type="password"], [type="url"]) {
font-family: inherit;
font-size: 1rem;
}
:is() والخصوصية
تفصيل حرج حول :is() هو سلوك خصوصيتها. خصوصية :is() تساوي المحدد الأكثر تحديدا في قائمة وسائطها. هذا يعني أنك إذا مزجت محدد فئة ومحدد معرف داخل :is() فإن تعبير :is() بالكامل يأخذ خصوصية محدد المعرف. هذا مهم لأنه قد يسبب زيادات خصوصية غير متوقعة إذا لم تكن حذرا.
سلوك خصوصية :is()
/* الخصوصية: (0, 1, 0) -- أعلى وسيط هو .intro (فئة) */
:is(.intro, .summary) p {
font-size: 1.125rem;
}
/* الخصوصية: (1, 0, 0) -- أعلى وسيط هو #hero (معرف) */
:is(#hero, .banner) p {
font-size: 1.5rem;
}
/* تحذير: حتى .banner p تحصل على خصوصية مستوى المعرف هنا! */
/* هذا يمكن أن يخلق مشاكل: */
:is(#main, .content) a { color: blue; } /* (1, 0, 1) */
.content a { color: red; } /* (0, 1, 1) */
/* القاعدة الأولى تفوز لـ .content a لأن #main رفع الخصوصية */
/* نمط آمن: حافظ على تناسق مستويات الخصوصية داخل :is() */
:is(.card, .panel, .modal) .title { /* كلها مستوى فئة: (0, 2, 0) */
color: var(--text-dark, #1a1a1a);
}
/* نمط محفوف بالمخاطر: مستويات خصوصية مختلطة */
:is(#sidebar, .aside, footer) a { /* مستوى معرف: (1, 0, 1) */
color: inherit;
}
:is(). يرث تعبير :is() بالكامل خصوصية الوسيط الأعلى خصوصية. هذا يعني أن :is(#hero, .banner) يعطي المطابقة بالكامل خصوصية مستوى المعرف حتى للعناصر المطابقة بواسطة .banner وحدها. إذا كنت بحاجة إلى تجميع بخصوصية صفرية، استخدم :where() بدلا من ذلك والتي سنغطيها تاليا.الفئة الزائفة :where()
الفئة الزائفة :where() مطابقة وظيفيا لـ :is() -- تقبل قائمة محددات مفصولة بفواصل وتطابق أي عنصر يطابق واحدا على الأقل منها. الفرق الحرج الوحيد هو الخصوصية: :where() لها دائما خصوصية صفرية بغض النظر عن المحددات داخلها. هذا يجعل :where() مثالية لكتابة التنسيقات الأساسية وملفات إعادة التعيين والتنسيقات الافتراضية التي تنوي تجاوزها بسهولة.
الخصوصية الصفرية في التطبيق العملي
لأن :where() لا تساهم بأي خصوصية، فإن أي محدد آخر بأدنى خصوصية سيتجاوزها. هذه ميزة وليست قيدا -- تعني أن التنسيقات المعرفة بـ :where() تعمل كافتراضيات يمكن لأي قاعدة أكثر تحديدا تجاوزها بدون الحاجة إلى تصعيد الخصوصية.
خصوصية :where() الصفرية
/* :where() لها خصوصية صفرية */
:where(.card, .panel, .modal) .title {
color: var(--text-dark, #1a1a1a); /* الخصوصية: (0, 1, 0) -- فقط .title تحسب */
}
/* قارن مع :is() */
:is(.card, .panel, .modal) .title {
color: var(--text-dark, #1a1a1a); /* الخصوصية: (0, 2, 0) -- .card + .title */
}
/* :where() لتنسيقات النماذج الافتراضية -- سهلة التجاوز */
:where(input, select, textarea) {
font-family: inherit;
font-size: 1rem;
line-height: 1.5;
/* الخصوصية: (0, 0, 0) -- أي قاعدة تتجاوز هذه */
}
/* حتى محدد عنصر بسيط يتجاوز :where() */
input {
font-size: 0.875rem; /* هذه تفوز على قاعدة :where() أعلاه */
/* الخصوصية: (0, 0, 1) تتغلب على (0, 0, 0) */
}
/* :where() مع المعرفات لا تزال بخصوصية صفرية */
:where(#hero, #main, #sidebar) a {
color: inherit; /* الخصوصية: (0, 0, 1) -- فقط "a" تحسب */
}
/* فئة واحدة تتجاوزها بسهولة */
.link {
color: blue; /* (0, 1, 0) تتغلب على (0, 0, 1) */
}
:where() لإعادة تعيين CSS والافتراضيات
حالة الاستخدام الأكثر طبيعية لـ :where() هي كتابة إعادة تعيين CSS والتنسيقات الافتراضية. غالبا ما تنشئ إعادة تعيين CSS التقليدية مشاكل خصوصية لأنها تستخدم محددات عناصر يجب أن تطابقها أو تتجاوزها التنسيقات اللاحقة. مع :where() تكون إعادة التعيين بخصوصية صفرية لذا كل تنسيق لاحق -- حتى تلك التي تستخدم محددات عناصر بسيطة -- يمكنه تجاوز الافتراضيات بدون أي معارك خصوصية.
إعادة تعيين CSS باستخدام :where()
/* إعادة تعيين CSS حديثة مع :where() -- خصوصية صفرية */
:where(*, *::before, *::after) {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:where(html) {
line-height: 1.5;
-webkit-text-size-adjust: 100%;
}
:where(body) {
min-height: 100vh;
}
:where(img, picture, video, canvas, svg) {
display: block;
max-width: 100%;
}
:where(input, button, textarea, select) {
font: inherit;
}
:where(h1, h2, h3, h4, h5, h6) {
line-height: 1.2;
overflow-wrap: break-word;
}
:where(p) {
overflow-wrap: break-word;
}
:where(a) {
color: inherit;
text-decoration: inherit;
}
:where(ul, ol) {
list-style: none;
}
/* كل ما سبق بخصوصية صفرية */
/* حتى أبسط محدد سيتجاوزها */
h1 { font-size: 2.5rem; } /* يتجاوز :where(h1) بسهولة */
:is() مقابل :where(): متى تستخدم كلا منهما
كلتا :is() و:where() توفران نفس وظيفة التجميع. الاختيار بينهما يعتمد على ما إذا كنت تريد أن تساهم المحددات المجمعة في الخصوصية أم لا. استخدم :is() عندما تكتب تنسيقات مكونات يجب أن تنافس بشكل طبيعي في شلال الخصوصية. استخدم :where() عندما تكتب تنسيقات أساسية أو افتراضيات أو طبقات أدوات يجب أن يسهل تجاوزها.
الاختيار بين :is() و :where()
/* استخدم :where() لـ: التنسيقات الأساسية وإعادة التعيين والافتراضيات */
/* يجب أن يسهل تجاوزها بتنسيقات المكونات */
:where(article, section, aside) {
padding: 1rem;
}
:where(.btn) {
display: inline-flex;
align-items: center;
padding: 0.5rem 1rem;
border: none;
cursor: pointer;
}
/* استخدم :is() لـ: تنسيقات المكونات والأنماط المحددة */
/* يجب أن تنافس بشكل طبيعي في الشلال */
:is(.card, .panel) .header {
font-weight: 600;
border-bottom: 1px solid var(--border-light, #eee);
}
:is(.nav-primary, .nav-secondary) a:is(:hover, :focus-visible) {
color: var(--primary, #0066cc);
}
/* نمط: :where() للقاعدة و :is() للتفاصيل */
/* زر أساسي (سهل التجاوز) */
:where(.btn) {
background: var(--bg-light, #f5f5f5);
color: var(--text-dark, #333);
}
/* متغير محدد (خصوصية عادية يتجاوز القاعدة) */
.btn:is(.btn-primary) {
background: var(--primary, #0066cc);
color: white;
}
:where() لجميع تنسيقات المكونات الافتراضية و:is() لتنسيقات المتغيرات والحالات. بهذه الطريقة تكون افتراضياتك دائما قابلة للتجاوز لكن متغيراتك وحالاتك المحددة تحمل خصوصية كافية لتجاوز الافتراضيات بشكل موثوق. على سبيل المثال: :where(.btn) { ... } للزر الأساسي و.btn:is(.primary, .secondary) { ... } للمتغيرات.الفئة الزائفة :has()
الفئة الزائفة :has() هي الأكثر ثورية على الأرجح بين الإضافات إلى محددات CSS في السنوات الأخيرة. تتيح لك تحديد عنصر بناء على ما يحتويه -- أبناؤه أو أحفاده أو حتى أشقاؤه اللاحقون. قبل :has() كان CSS يستطيع فقط تحديد العناصر بناء على أسلافها وأشقائها السابقين. لم يكن هناك طريقة لتنسيق عنصر أب بناء على أبنائه وهذا هو السبب في أن المطورين كثيرا ما أطلقوا على "محدد الأب" الميزة الأكثر طلبا في CSS. تقدم الفئة الزائفة :has() أخيرا هذه القدرة وأكثر بكثير.
:has() الأساسية كمحدد أب
الاستخدام الأكثر شيوعا لـ :has() هو كمحدد أب: تحديد عنصر أب بناء على وجود ابن أو حفيد محدد. الصيغة هي parent:has(child) حيث يتم تحديد الأب فقط إذا كان يحتوي على عنصر يطابق محدد الابن.
:has() كمحدد أب
/* تحديد البطاقات التي تحتوي على صورة */
.card:has(img) {
display: grid;
grid-template-rows: auto 1fr;
}
/* البطاقات بدون صور تحصل على تخطيط مختلف */
.card:not(:has(img)) {
padding: 2rem;
}
/* تحديد مجموعات النموذج التي تحتوي على مدخل مطلوب */
.form-group:has(input:required) {
position: relative;
}
/* إضافة نجمة للتسميات داخل مجموعات النموذج المطلوبة */
.form-group:has(input:required) label::after {
content: " *";
color: red;
}
/* تنسيق قسم بشكل مختلف إذا احتوى على عنوان */
section:has(h2) {
padding-top: 2rem;
border-top: 2px solid var(--primary, #0066cc);
}
/* تحديد التنقل الذي يحتوي على أكثر من قائمة */
nav:has(ul + ul) {
display: flex;
justify-content: space-between;
}
/* تحديد المقالات التي تحتوي على كتل كود */
article:has(pre code) {
font-size: 0.95rem;
}
:has() مع مجمع الابن المباشر
يمكنك استخدام المجمعات داخل :has() لتكون أكثر دقة حول العلاقة. مجمع الابن المباشر (>) يضمن أنك تتحقق فقط من الأبناء المباشرين وليس جميع الأحفاد.
:has() مع المجمعات
/* تحديد القوائم التي لديها ابن مباشر بفئة "active" */
ul:has(> .active) {
background: var(--bg-light, #f8f9fa);
}
/* تحديد div يحتوي مباشرة على h1 */
div:has(> h1) {
margin-bottom: 2rem;
}
/* حاويات بأزرار أبناء مباشرين */
.toolbar:has(> button) {
display: flex;
gap: 0.5rem;
padding: 0.5rem;
}
/* تحديد fieldsets التي تحتوي مباشرة على legend + inputs */
fieldset:has(> legend):has(> input) {
border: 2px solid var(--border-light, #ddd);
border-radius: 0.5rem;
padding: 1.5rem;
}
:has() لتحديد الأشقاء
الفئة الزائفة :has() ليست مقتصرة على علاقات الأب-الابن. يمكنك استخدام مجمع الأخ المجاور (+) أو مجمع الأخ العام (~) داخل :has() لتحديد العناصر بناء على أشقائها اللاحقين. هذه قدرة لم يستطع أي محدد CSS سابق توفيرها -- تحديد عنصر بناء على ما يأتي بعده.
:has() مع محددات الأشقاء
/* تنسيق عنوان يتبعه فقرة */
h2:has(+ p) {
margin-bottom: 0.5rem;
}
/* تنسيق عنوان لا يتبعه فقرة */
h2:not(:has(+ p)) {
margin-bottom: 1.5rem;
}
/* تنسيق صورة يتبعها figcaption */
img:has(+ figcaption) {
border-radius: 0.5rem 0.5rem 0 0;
}
/* تنسيق تسمية لديها مدخل شقيق محدد */
label:has(~ input:checked) {
font-weight: bold;
color: var(--primary, #0066cc);
}
/* تنسيق مدخل يتبعه رسالة خطأ */
input:has(+ .error-message) {
border-color: red;
background: #fff0f0;
}
/* تنسيق dt عندما يحتوي شقيقه dd على محتوى محدد */
dt:has(+ dd img) {
font-size: 1.25rem;
}
:has() لتنسيقات التحقق من النماذج
أحد أكثر التطبيقات العملية لـ :has() هو تنسيق النماذج بناء على حالة التحقق. يمكنك تنسيق مجموعات نماذج كاملة وتسميات ونصوص مساعدة وحاويات محيطة بناء على ما إذا كانت المدخلات صالحة أو غير صالحة أو مركز عليها أو ممتلئة. كان هذا مستحيلا سابقا بدون JavaScript -- CSS وحده كان يستطيع فقط تنسيق المدخل نفسه وليس السياق المحيط به.
التحقق من النماذج مع :has()
/* تنسيق مجموعة النموذج عندما يكون مدخلها مركزا عليه */
.form-group:has(input:focus, textarea:focus, select:focus) {
background: var(--bg-light, #f8f9fa);
border-radius: 0.5rem;
padding: 0.75rem;
}
/* تمييز التسمية عندما يكون المدخل مركزا عليه */
.form-group:has(input:focus) label {
color: var(--primary, #0066cc);
font-weight: 600;
}
/* تنسيق مجموعة النموذج عندما يكون المدخل غير صالح وتم التفاعل معه */
.form-group:has(input:invalid:not(:placeholder-shown)) {
border-left: 3px solid red;
padding-left: 1rem;
}
/* إظهار نص الخطأ فقط عندما يكون المدخل غير صالح */
.form-group:has(input:invalid:not(:placeholder-shown)) .error-text {
display: block;
color: red;
font-size: 0.875rem;
margin-top: 0.25rem;
}
/* إخفاء نص الخطأ افتراضيا */
.form-group .error-text {
display: none;
}
/* تنسيق مجموعة النموذج عندما يكون المدخل صالحا */
.form-group:has(input:valid:not(:placeholder-shown)) {
border-left: 3px solid green;
padding-left: 1rem;
}
/* تنسيق النموذج بالكامل عندما يحتوي على مدخلات غير صالحة */
form:has(input:invalid) .submit-btn {
opacity: 0.5;
cursor: not-allowed;
}
/* تنسيق النموذج عندما تكون جميع المدخلات المطلوبة صالحة */
form:not(:has(input:required:invalid)) .submit-btn {
opacity: 1;
cursor: pointer;
background: var(--primary, #0066cc);
}
/* مجموعة مربع الاختيار: تنسيق الأب عند التحديد */
.checkbox-wrapper:has(input[type="checkbox"]:checked) {
background: var(--primary-light, #e6f0ff);
border-color: var(--primary, #0066cc);
}
/* مجموعة زر الراديو: تمييز الخيار المحدد */
.radio-option:has(input[type="radio"]:checked) {
background: var(--primary-light, #e6f0ff);
border: 2px solid var(--primary, #0066cc);
font-weight: 600;
}
:invalid:not(:placeholder-shown) هو طريقة ذكية لتجنب إظهار أخطاء التحقق على الحقول الفارغة قبل أن يبدأ المستخدم بالكتابة. الفئة الزائفة :placeholder-shown تطابق المدخلات التي يكون نص العنصر النائب مرئيا فيها حاليا (بمعنى أن المستخدم لم يدخل أي قيمة). بدمجها مع :not() تطبق تنسيقات عدم الصلاحية فقط بعد أن يكتب المستخدم شيئا ويكون لا يزال غير صالح.:has() للتخطيطات الشرطية
يفتح محدد :has() أنماط تخطيط جديدة تماما. يمكنك تغيير تكوين الشبكة أو الصندوق المرن لحاوية بناء على عدد أو نوع أبنائها. يمكنك تطبيق تنسيقات مختلفة على صفحة بناء على المكونات التي تحتويها. هذا هو CSS الشرطي حقا -- التخطيط يتكيف بناء على المحتوى وليس فقط حجم إطار العرض.
تخطيطات شرطية مع :has()
/* تغيير تخطيط الشبكة بناء على وجود شريط جانبي */
.page:has(.sidebar) {
display: grid;
grid-template-columns: 1fr 300px;
gap: 2rem;
}
.page:not(:has(.sidebar)) {
max-width: 800px;
margin-inline: auto;
}
/* تخطيط البطاقة يتغير بناء على نوع المحتوى */
.card:has(img):has(.card-body) {
display: grid;
grid-template-columns: 200px 1fr;
align-items: start;
}
.card:has(img):not(:has(.card-body)) {
text-align: center;
}
/* تعديل التنقل لكميات مختلفة من العناصر */
nav:has(li:nth-child(6)) {
/* أكثر من 5 عناصر: استخدم تخطيطا مختلفا */
flex-wrap: wrap;
justify-content: center;
}
/* تنسيق الحالة الفارغة */
.list:not(:has(li)) {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
background: var(--bg-light, #f8f9fa);
color: var(--text-light, #666);
}
.list:not(:has(li))::before {
content: "لا توجد عناصر للعرض";
font-style: italic;
}
/* تبديل الوضع الداكن بناء على مربع اختيار */
body:has(#dark-mode-toggle:checked) {
--bg-light: #1a1a2e;
--bg-white: #16213e;
--text-dark: #e0e0e0;
--text-light: #aaa;
--border-light: #333;
}
دمج المحددات الحديثة
تظهر القوة الحقيقية للمحددات الحديثة عند دمجها. يمكنك استخدام :is() للتجميع و:not() للاستبعاد و:where() لافتراضيات بخصوصية صفرية و:has() للتحديد العلائقي/الأبوي -- كل ذلك في نفس ملف التنسيق وأحيانا في نفس المحدد. تتكون هذه الفئات الزائفة بشكل طبيعي مما يتيح لك التعبير عن منطق تحديد معقد كان مستحيلا أو غير عملي مع CSS القديم.
دمج جميع المحددات الحديثة
/* البطاقات التي بها صور لكن ليست البطاقة المميزة */
.card:has(img):not(.featured) {
grid-template-columns: 120px 1fr;
}
/* تنسيقات أساسية مع :where() معززة بـ :is() */
:where(.card) {
padding: 1rem;
border: 1px solid var(--border-light, #eee);
}
:is(.card):is(:hover, :focus-within) {
border-color: var(--primary, #0066cc);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* تنسيق العناوين داخل حاويات بمحتوى محدد */
:is(section, article):has(.important) :is(h2, h3) {
color: var(--primary, #0066cc);
border-left: 3px solid currentColor;
padding-left: 0.75rem;
}
/* مجموعات النموذج غير المعطلة والتي بها مدخلات مركز عليها */
.form-group:not(:has(:disabled)):has(:focus) {
outline: 2px solid var(--primary, #0066cc);
outline-offset: 4px;
border-radius: 0.375rem;
}
/* افتراضيات :where() التي يمكن لـ :has() تعزيزها */
:where(.grid) {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
/* عندما تحتوي الشبكة على عناصر عريضة اضبط */
.grid:has(.wide-item) {
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
}
/* نمط تنقل معقد */
:is(nav, .nav):has(ul):not(:has(.mobile-menu-open)) ul {
display: flex;
gap: 1rem;
list-style: none;
}
/* إدارة التركيز سهلة الوصول */
:is(a, button, input, select, textarea):focus-visible:not(:has(*)) {
outline: 2px solid var(--primary, #0066cc);
outline-offset: 2px;
}
بناء نظام مكونات
دعونا نلقي نظرة على كيفية استخدام المحددات الحديثة لبناء نظام مكونات كامل ومتكيف. هذا المثال يوضح مكون بطاقة يتكيف تخطيطه بناء على محتواه وحالاته وسياقه -- كل ذلك بدون JavaScript أو أسماء فئات إضافية.
مكون بطاقة متكيف
/* بطاقة أساسية مع :where() لسهولة التجاوز */
:where(.card) {
border: 1px solid var(--border-light, #e0e0e0);
border-radius: 0.75rem;
overflow: hidden;
background: var(--bg-white, #ffffff);
}
/* بطاقة بصورة: تخطيط أفقي */
.card:has(> img, > .card-image) {
display: grid;
grid-template-columns: clamp(120px, 30%, 250px) 1fr;
}
/* بطاقة بدون صورة: تخطيط مكدس */
.card:not(:has(img)) {
display: flex;
flex-direction: column;
padding: var(--card-padding, 1.5rem);
}
/* بطاقة بعناصر كثيرة: اضبط التباعد */
.card:has(.card-body > *:nth-child(4)) .card-body {
gap: 0.5rem;
}
/* حالات البطاقة باستخدام :is() للتجميع */
.card:is(:hover, :focus-within):not(.disabled) {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
transition: all 0.2s ease;
}
/* إجراءات البطاقة: تغيير تنسيق الزر بناء على سياق البطاقة */
.card:has(.card-badge.urgent) :is(.card-action, .card-btn) {
background: #dc3545;
color: white;
}
/* سلوك متجاوب داخل الشبكة */
.card-grid:has(.card:nth-child(4)) {
grid-template-columns: repeat(auto-fill, minmax(min(100%, 280px), 1fr));
}
.card-grid:not(:has(.card:nth-child(4))) {
grid-template-columns: repeat(auto-fill, minmax(min(100%, 350px), 1fr));
}
دعم المتصفحات
يتفاوت دعم المتصفحات للمحددات الحديثة حسب الميزة. الفئة الزائفة :not() مع محدد بسيط واحد مدعومة منذ CSS3 وتعمل في كل مكان. :not() المحسنة مع وسائط متعددة ومحددات معقدة مدعومة في جميع المتصفحات الحديثة بما في ذلك Chrome 88+ و Firefox 84+ و Safari 14+ و Edge 88+. تشترك الفئتان الزائفتان :is() و:where() في نفس الدعم: Chrome 88+ و Firefox 82+ و Safari 14+ و Edge 88+. الفئة الزائفة :has() كانت الأخيرة في الوصول ومدعومة في Chrome 105+ و Firefox 121+ و Safari 15.4+ و Edge 105+. اعتبارا من عام 2025 جميع المحددات الأربعة آمنة للاستخدام في الإنتاج لمشاريع الويب الحديثة.
:has() ضع في اعتبارك أنها قد تؤثر على الأداء إذا استخدمت مع محددات واسعة جدا. يجب على المتصفح تقييم شرط :has() كلما تغير DOM لذا *:has(.something) يجبر المتصفح على فحص كل عنصر. لأفضل أداء حدد دائما محددات :has() بعنصر أو فئة محددة مثل .card:has(img) بدلا من *:has(img) أو :has(img) وحدها.:is() و:where() تتصرفان بشكل متطابق. بينما تطابقان نفس العناصر فإن خصوصيتهما مختلفة تماما. إذا كتبت تنسيقات افتراضية بـ :is() عندما كنت تقصد استخدام :where() فقد يكون من الصعب بشكل غير متوقع تجاوز تلك الافتراضيات. وعلى العكس إذا استخدمت :where() لتنسيقات مكونات تحتاج لتجاوز التنسيقات الأساسية فقد تكون ضعيفة جدا. اسأل نفسك دائما: "هل يجب أن يساهم هذا المحدد في الخصوصية؟" إذا نعم استخدم :is(). إذا لا استخدم :where().تمرين عملي
ابنِ مكون نموذج متكيف يستخدم جميع المحددات الحديثة الأربعة. أنشئ نموذج HTML بالبنية التالية: ثلاثة مدخلات نصية (الاسم والبريد الإلكتروني والرسالة) ومربعي اختيار (الشروط والنشرة الإخبارية) ومجموعة أزرار راديو بثلاثة خيارات (دعم ومبيعات وعام) وزر إرسال. كل مدخل يجب أن يكون ملفوفا في div بفئة .form-group مع تسمية وعنصر .error-text اختياري. الآن اكتب CSS يحقق ما يلي باستخدام المحددات الحديثة فقط -- بدون JavaScript: (1) استخدم :where() لتعيين تنسيقات افتراضية بخصوصية صفرية لجميع عناصر النموذج (الخط والحشوة والحدود ونصف قطر الحدود). (2) استخدم :is() لتجميع تنسيقات التحويم والتركيز لجميع العناصر التفاعلية (المدخلات والقوائم المنسدلة ومناطق النص والأزرار) حتى تشترك في تنسيق حلقة تركيز مشترك. (3) استخدم :not() لتنسيق جميع عناصر .form-group عدا الأخير بهامش سفلي وحدود. استخدم :not(:disabled) لضمان أن المدخلات الممكنة فقط تحصل على التنسيقات التفاعلية. (4) استخدم :has() لتنسيق حاويات .form-group بناء على حالة مدخلها: مييز المجموعة عند التركيز على مدخلها واعرض حدا أيسر أخضر عندما يكون المدخل صالحا وغير فارغ واعرض حدا أيسر أحمر عندما يكون المدخل غير صالح وغير فارغ واعرض .error-text فقط عندما يكون المدخل في حالة غير صالحة. (5) استخدم :has() لتنسيق زر الإرسال -- اجعله يبدو معطلا (شفافية مخفضة ومؤشر غير مسموح) عندما يحتوي النموذج على أي مدخلات مطلوبة غير صالحة وقابلا للتشغيل بالكامل عندما تكون جميع الحقول المطلوبة صالحة. (6) استخدم :has() مع مدخلات مربع الاختيار والراديو لتمييز غلاف الخيار المحدد. (7) ادمج :is() و:not() و:has() في محددين على الأقل لتوضيح كيف تتركب. اختبر نموذجك بملء الحقول وتركها فارغة وإدخال بريد إلكتروني غير صالح وتحديد المربعات للتحقق من أن جميع الحالات المرئية تتحدث بشكل صحيح بدون أي JavaScript.