Исторически селекторы 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);
});
}
}