سبک‌بندی مبتنی بر وضعیت فرزند

ویژگی‌های وب: :has()‎ :not()‎

از گذشته، انتخابگرهای CSS فقط می‌توانستند به سمت پایین حرکت کنند—شما می‌توانستید یک فرزند را براساس والد آن سبک‌دهی کنید، اما نمی‌توانستید یک والد را براساس فرزند آن سبک‌دهی کنید. کلاس کاذب :has() این را تغییر می‌دهد و به شما امکان می‌دهد تا به‌طور شرطی به عنصر نگهدارنده بسته به حضور یا وضعیت فرزندانش سبک بدهید.

با ترکیب کردن :has() با شبه‌کلاس‌های حالت (مثل :checked، :focus، :valid، :invalid، یا :not())، می‌توانید مؤلفه‌های واسط کاربر تعاملی و پیچیده را کاملاً در CSS بسازید، بدون اینکه برای جابه‌جایی کلاس‌های «اصلاح‌کننده» (مثل .is-active یا .has-error) در عناصر والد به جاوا اسکریپت نیاز داشته باشید.

این امر به‌ویژه برای مؤلفه‌هایی مفید است که باید به تعاملات داخلی پاسخ دهند، مانند کلید تغییر وضعیت زمینه بومی‌سازی‌شده که به چارگوش انتخاب واکنش نشان می‌دهد (:checked)، گروه فرمی که خطایی را برجسته می‌کند (:invalid)، یا کارتی که وقتی پیوند فرعی در کانون توجه قرار می‌گیرد، بالا می‌رود (:focus-within).

پیاده‌سازی سبک محفظه مبتنی بر وضعیت

اجباری: باید از انتخابگر :has() در عنصر نگهدارنده استفاده کنید تا وضعیت خاص (مثلاً :checked، :focus، :invalid) عنصر فرزند تعاملی آن را شناسایی کنید.

برای ساختن مؤلفه‌ای که سبک آن براساس وضعیت فرزند تغییر می‌کند:

  1. تعریف سبک پیش‌فرض: متغیرهای CSS را روی محفظه تنظیم کنید تا حالت پایه آن تعریف شود.
  2. اعمال کردن لغوهای مبتنی بر وضعیت: ظرف را با :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() نیز استفاده کنید تا آن کلاس را براساس رویداد جاوا اسکریپت معادل (مثلاً 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);
    });
  }
}