基于子状态的样式设置

Web 功能: :has() :not()

过去,CSS 选择器只能向下遍历,即您可以根据父级元素为子级元素设置样式,但不能根据子级元素为父级元素设置样式。:has() 伪类改变了这一点,让您可以根据后代元素的存在或状态,有条件地设置容器元素的样式。

通过将 :has() 与状态伪类(例如 :checked、:focus、:valid、:invalid 或 :not())结合使用,您可以完全在 CSS 中构建复杂的交互式界面组件,而无需使用 JavaScript 在父元素上切换“修饰符”类(例如 .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() 可用作渐进增强,无需后备。

强制性要求:在为关键功能实现后备方案时,您必须在 CSS 中使用 @supports not selector(:has(*)) 来定义传统的基于类的后备方案。如果关键状态更改依赖于用户互动,您还必须使用带有 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);
    });
  }
}