оригинальная версия версия для слабовидящих контрастная версия выключить изображения включить изображения тёмная версия светлая версия RSS FEED K2 NEWS
☰☰
Пятница, 06 Ноябрь 2026 00:00

Селектор :has()

Лет десять назад фраза «в CSS нельзя выбрать родителя по его потомку» считалась аксиомой, и любой вёрстальщик знал её наизусть. Нужно подсветить карточку, в которой есть картинка? Добавляй класс на сервере или через JavaScript. Нужно выделить поле формы, если внутри него невалидный input? Снова скрипт. Я сам годами писал такие костыли и считал это нормой. Оказалось, что нормой это быть перестало ещё пару лет назад: селектор :has() работает во всех современных браузерах, и половину этих скриптов можно спокойно удалить.

Родительский селектор в CSS: :has() наконец работает

Идея у :has() простая: он выбирает элемент, внутри которого (или сразу после которого) есть то, что указано в скобках. Читается почти как обычная речь. Запись .card:has(img) означает «карточка, внутри которой есть картинка». Никаких дополнительных классов, никакого JS, достаточно одной строки в таблице стилей:


.card:has(img) {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 1rem;
}

Раньше для этого приходилось делать два шаблона карточки или вешать модификатор вроде .card--with-image. Теперь вёрстка подстраивается под содержимое сама: добавили картинку в новость, и карточка перестроилась, убрали, и она вернулась к обычному виду. Для CMS, где редактор может вставить что угодно, это особенно удобно, потому что вы больше не зависите от того, не забыл ли он отметить нужный чекбокс.

Второй сценарий, где :has() сильно выручает, это формы. Состояние поля в CSS можно было узнать только у самого input, а подсветить хотелось всю обёртку: рамку, подпись, иконку. Теперь это делается так:


.field:has(input:focus-visible) {
  border-color: var(--accent-color);
}

.field:has(input:invalid:not(:placeholder-shown)) {
  border-color: #c0392b;
}

Обратите внимание на второй селектор: ошибка подсвечивается только тогда, когда пользователь уже что-то ввёл. Без проверки :placeholder-shown пустое обязательное поле было бы красным сразу при загрузке страницы, и выглядело бы это довольно агрессивно. Для небольших форм обратной связи такого набора правил хватает, чтобы вообще не писать скрипт валидации оформления.

А вот трюк, который понравился мне больше всего: переключатель темы вообще без JavaScript. Если помните прошлый обзор про custom properties, там тему переключал атрибут data-theme на html. Теперь тот же эффект даёт обычный чекбокс:


:root {
  --bg-color: #ffffff;
  --text-color: #1f2d27;
}

:root:has(#theme-toggle:checked) {
  --bg-color: #1a1f1c;
  --text-color: #e8efe9;
}

Пользователь ставит галочку, корневой элемент получает переопределённые переменные, и весь сайт перекрашивается. Минус очевиден: состояние чекбокса не сохраняется между визитами, поэтому для реального проекта я всё равно оставляю localStorage. Но как базовый уровень или быстрый прототип такой способ отлично работает, а главное, наглядно показывает, насколько :has() расширил возможности чистого CSS.

Умеет :has() и смотреть на соседей, а не только на потомков. Например, нужно убрать лишний отступ у заголовка, за которым сразу идёт подзаголовок:


h2:has(+ h3) {
  margin-bottom: 0.25rem;
}

Раньше такая задача решалась только «снизу вверх», через стили у h3, и достучаться до предыдущего элемента было невозможно. Теперь направление уже не ограничено, и это, пожалуй, самое важное изменение в логике селекторов за много лет.

С поддержкой сейчас всё благополучно: Chrome и Edge понимают :has() с версии 105, Safari с 15.4, а Firefox догнал остальных в конце 2023 года. Для проектов, где нужна подстраховка, есть проверка через @supports selector(:has(*)): внутри неё можно размещать «умные» правила, а за её пределами оставлять простое оформление по умолчанию. Я обычно так и делаю на страницах с большой аудиторией.

Подводные камни тоже есть. Во-первых, :has() нельзя вкладывать в другой :has(), а внутри него нельзя использовать псевдоэлементы вроде ::before. Во-вторых, широкие запросы типа *:has(a) заставляют браузер проверять огромное количество элементов, поэтому я бы не рекомендовал ставить :has() на body или универсальный селектор без необходимости. Лучше всегда сужать область: .card:has(img), а не просто *:has(img). В-третьих, специфичность селектора определяется самым «тяжёлым» из аргументов в скобках, так что при переопределении старых стилей это стоит учитывать, чтобы потом не искать, почему правило не применилось.

Если подвести итог, :has() не заменяет JavaScript целиком, но убирает целый класс мелких задач, ради которых раньше приходилось подключать скрипты: модификаторы по содержимому, состояние форм, соседние элементы. Если у вас на сайте остались такие костыли, самое время вернуться к ним и посмотреть, нельзя ли заменить их одной строкой CSS. Как правило, можно.

Спонсоры: