Помните ту боль, когда для тени на карточке из-за фокуса кнопки приходилось лезть в HTML и добавлять родителю модификатор? BEM-пуристы хватались за голову, а разработчики плодили классы в JS или натыкали pointer-events.
:has() решает это без единого хака и без изменения разметки.Проблема: состояние потомка -> стиль родителя
Типичный production-кейс: карточка товара в e-commerce или лендинге. Внутри кнопка "Купить" в фокусе. Нужно визуально подсветить всю карточку, например тенью. Без
:has() — только JS-навешивание класса на родителя или BEM-модификатор, который загрязняет HTML. :has() делает это строго в CSS:.card:has(.card__button:focus) {
box-shadow: 0 0 0 4px blue;
}
Реальный кейс: формы с валидацией
Есть список
<ul>, внутри <li> с инпутом, у которого aria-invalid="true". Нужно выделить весь <li> красной рамкой без дополнительных классов. Решение:li:has(input[aria-invalid="true"]) {
border: 2px solid red;
background: #ffe0e0;
}
Это работает без JS, не трогает HTML и делает стили зависимыми от реального состояния DOM, а не от модификаторов.
Где :has() реально спасает в production
* Дропдауны и аккордеоны — стили родителя меняются при открытии вложенного состояния.
* Карточки с разным контентом — разный паддинг или border в зависимости от вложенного блока (например,
.card:has(> .image)).* Формы с ошибками — родитель подкрашивается сам, когда инпут в invalid или focus.
Типичная ошибка: забыть про специфичность
:has() — псевдокласс, он не увеличивает специфичность родителя. Это значит, селектор .card:has(.button) имеет ту же весомость, что и .card. Не комбинируйте его с другими псевдоклассами бездумно. И помните: Edge до 89 и Opera Mini не поддерживают :has(). Для критичных интерфейсов — делайте fallback, например, через CSS-хак с @supports или базовый стиль без :has().Вывод:
:has() переводит CSS с уровня "реакция на классы" на уровень "реакция на реальное состояние DOM", уменьшая связанность HTML и стилей, и избавляет от BEM-костылей в production-верстке.