過去,CSS 選取器只能向下周遊,您可以根據父項設定子項的樣式,但無法根據子項設定父項的樣式。:has() 虛擬類別會改變這種情況,讓您根據子系的存在或狀態,有條件地設定容器元素的樣式。
只要將 :has() 與狀態虛擬類別 (例如 :checked、:focus、:valid、:invalid 或 :not()) 結合,就能完全使用 CSS 建構複雜的互動式 UI 元件,不必使用 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() 可做為漸進式增強功能,不需備用。
強制:為重要功能實作備用方案時,必須在 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);
});
}
}