Запрос на «родительский селектор» – возможность стилизовать элемент в зависимости от его содержимого – разработчики повторяли больше десяти лет: CSS умел выбирать потомков по родителю, но не наоборот. Псевдокласс :has() из спецификации Selectors Level 4 закрыл эту потребность и оказался заметно шире. Формально это реляционный псевдокласс: он проверяет условие относительно элемента – наличие потомка, следующего соседа, состояние поля формы – и применяет стили, только когда условие выполнено. В паре с псевдоклассом отрицания :not() он закрывает сценарии, ради которых раньше подключали JavaScript и навешивали классы-флаги.
Как это работает
Синтаксис – селектор:has(аргумент). Браузер «якорит» проверку на элементе слева: section:has(img) выберет секцию, внутри которой на любой глубине есть изображение; сама картинка при этом не стилизуется. Аргумент – список относительных селекторов через запятую, причём список forgiving («прощающий»): невалидный селектор в нём игнорируется и не ломает правило.
Комбинаторы внутри аргумента меняют смысл проверки:
:has(img)– потомок на любой глубине;:has(> img)– только прямой потомок;h1:has(+ h2)– «предыдущий сосед»: выбирается заголовок, сразу за которым идёт h2;:has(video, audio)– логическое ИЛИ; цепочка:has(video):has(audio)– И.
Типовые применения в одном блоке кода:
/* Родитель по ребёнку: карточка с изображением и без */
.card:has(.card__image) { display: flex; gap: 1rem; }
.card:not(:has(.card__image)) { border-top: 3px solid #7c93e9; }
/* Подсветка группы полей формы при ошибке */
.form-group:has(:invalid) { border-color: #d33; }
/* Quantity query: от 5 элементов – плитка мельче */
.wrapper:has(.item:nth-last-child(n + 5)) { --item-size: 120px; }Последний пример – так называемый quantity query: связка с :nth-last-child() меняет стили контейнера в зависимости от количества дочерних элементов. До :has() это делалось только скриптом.
:has() и классы-флаги из JavaScript – в чём разница
| Критерий | :has() | JS + классы-флаги |
|---|---|---|
| Логика | живёт в CSS, одно правило | разнесена между скриптом и стилями |
| Разметка | чистая, без модификаторов | служебные классы вида has-image |
| Изменения DOM | браузер пересчитывает стили сам | нужен MutationObserver или повторный вызов |
| Отказоустойчивость | работает без JavaScript | ломается, если скрипт упал |
Ограничения зафиксированы в спецификации: вкладывать :has() внутрь :has() нельзя, а псевдоэлементы вроде ::before невалидны и внутри аргумента, и как якорь. В браузере без поддержки селектор с :has() инвалидирует всё правило (вне forgiving-обёрток :is() и :where()), поэтому для прогрессивного улучшения используют проверку @supports selector(:has(a)).
Специфичность :has() равна специфичности самого «сильного» селектора в аргументе – сам псевдокласс ничего не добавляет. div:has(#promo) весит как селектор с ID. Как это влияет на каскад – в статье о специфичности CSS.
Зачем это нужно
Практическая ценность – меньше JavaScript и чище разметка. До :has() реакция родителя на содержимое требовала скрипта, который следил за DOM и переключал классы; теперь то же самое делает браузер. Типовые задачи:
- навигация – подсветить пункт меню, содержащий активную ссылку:
li:has(.current); - карточки – разные макеты для карточки с изображением и без, без модификаторов в разметке;
- формы – стилизация label и целых групп по состояниям
:checked,:invalid,:focusвложенных полей; - глобальные состояния – реакция страницы на виджет:
:root:has([aria-expanded="true"]), удобно в связке с aria-expanded.
Цена вопроса – производительность: браузеру приходится обходить поддерево якорного элемента при каждой перепроверке. Рекомендации MDN и команды Chrome: не якорить :has() на body, :root или универсальном селекторе * и сужать область проверки комбинаторами > и + – .container:has(> img) вместо .container:has(img).
| Браузер | Версия | Дата |
|---|---|---|
| Safari | 15.4 | март 2022, первый браузер с поддержкой |
| Chrome, Edge | 105 | август 2022 |
| Firefox | 121 | декабрь 2023 |
| Baseline | Newly available | декабрь 2023 |
| Baseline | Widely available | ориентировочно июнь 2026, через 30 месяцев |
Глобальная поддержка по caniuse – около 92,7% на июль 2026. Не поддерживают :has() только IE и Opera Mini. Если проект обязан работать в устаревших браузерах, базовые стили пишут без :has(), а улучшения заворачивают в блок @supports selector(:has(a)) – тогда правило с неподдерживаемым селектором не инвалидирует остальной CSS.
Пример
Классическая задача форм: показать дополнительное поле, когда пользователь выбрал в списке вариант «Другое».
.other-field { display: none; }
form:has(option[value="other"]:checked) .other-field {
display: block;
}Раньше здесь понадобился бы обработчик change, который навешивает класс на форму. Одно CSS-правило заменяет обработчик события и продолжает работать, даже если JavaScript отключён или не успел загрузиться. По тому же принципу подсвечивают подпись активного поля – label:has(+ input:focus).
В практике с 1С-Битрикс :has() экономит время на типовых компонентах: они генерируют фиксированную разметку, и чтобы добавить класс-модификатор, шаблон компонента пришлось бы копировать и править. С :has() обёртку стилизуют по содержимому – например, прячут блок фильтра, в котором не осталось ни одного пункта, – одним правилом в CSS, без правки template.php.