$ sudo teach IT

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

После этого урока вы сможете выбирать и стилизовать элементы в зависимости от их содержимого — на чистом CSS, без JavaScript и без лишних классов в разметке.

Что уже умеем / что добавим

Вы умеете выбирать элементы по состоянию, положению, содержимому-декору и хранить значения в переменных. Но выбрать родитель по его содержимому вы пока не могли. Теперь добавим :has() — «родительский» селектор. В сквозном проекте мы выделим карточку услуги, внутри которой есть метка «Хит», не помечая её отдельным классом.

Что вы узнаете в рамках статьи

  • Что делает селектор :has() и почему его называют «родительским».
  • Как выбрать элемент по наличию определённого потомка.
  • Как комбинировать :has() с другими селекторами и состояниями.
  • Какие практические задачи он решает без JavaScript.

Ключевые термины

  • :has() — псевдокласс, выбирающий элемент по наличию подходящего содержимого.
  • Родительский селектор — селектор, выбирающий элемент на основе его потомков.
  • Условие внутри :has() — селектор, который должен найтись внутри элемента.
  • Комбинация селекторов — совмещение :has() с классами, состояниями и соседями.

Что такое :has() и почему он «родительский»

:has() — это псевдокласс, который проверяет, есть ли внутри элемента что-то, подходящее под указанный внутри селектор. Если есть — правило применяется к самому внешнему элементу. Читается это как «элемент, который имеет внутри …»:

.card:has(img) {
  padding: 0;
}

Правило применится к каждой карточке .card, внутри которой есть тег img. Обратите внимание: стилизуется именно карточка (внешний элемент), а условием служит её содержимое (img). Раньше это было невозможно — CSS умел идти только «вниз», от родителя к потомку. :has() позволяет посмотреть «внутрь» и принять решение о родителе. Поэтому его давно ждали и называли «родительским селектором».

Выбор по наличию потомка

Самый частый сценарий — применить стиль к контейнеру, если в нём есть определённый элемент. Например, убрать отступ у абзаца, если следом идёт список, или выделить блок с изображением:

.field:has(.error) {
  border-color: #e0473e;
}

Здесь поле формы .field получит красную рамку, только если внутри него есть элемент с классом .error (сообщение об ошибке). Не нужно добавлять классу поля модификатор через скрипт — CSS сам увидит ошибку внутри и подсветит поле. Это очень удобно для форм: наличие ошибки, подсказки или обязательной метки внутри поля меняет вид всего поля автоматически.

Комбинирование с другими селекторами

:has() становится по-настоящему мощным в связке с другими селекторами и состояниями. Внутри скобок можно использовать любые селекторы, а сам :has() — присоединять к классам, псевдоклассам и комбинаторам соседства:

/* Карточка, внутри которой есть бейдж */
.card:has(.badge) {
  outline: 2px solid #2d6cdf;
}

/* Метка рядом с полем в фокусе */
.field:has(input:focus) {
  background: #f0f6ff;
}

Первое правило обводит любую карточку, содержащую бейдж. Второе — подсвечивает поле, когда вложенный input в фокусе: :has() внутри содержит другой псевдокласс (:focus), и вместе они дают «родитель, чей потомок в фокусе». Такой приём заменяет то, что раньше требовало JavaScript-обработчиков. Можно комбинировать и с соседями: h2:has(+ p) выберет заголовок, за которым следует абзац.

Практические сценарии без JavaScript

Ценность :has() в том, что он убирает целый пласт мелких скриптов. Вот типичные задачи, которые теперь решаются одним селектором:

/* Убрать нижнюю рамку у пункта меню, содержащего подменю */
.menu-item:has(.submenu) {
  border-bottom: none;
}

Раньше, чтобы отличить пункты с подменю, разметку помечали классом вручную или через скрипт. С :has() CSS сам определяет такие пункты по содержимому. Похожим образом делают: подсветку строки таблицы с отмеченным чекбоксом (tr:has(input:checked)), особый вид галереи с одной картинкой против нескольких, реакцию контейнера на пустое или заполненное поле. Общий принцип один: если оформление зависит от того, что находится внутри, :has() почти наверняка справится без единой строки JavaScript.

Применяем в проекте

Выделим карточки услуг, внутри которых есть метка «Хит», — обведём их акцентной рамкой, чтобы популярные услуги бросались в глаза. Нам не нужен отдельный класс на «горячих» карточках: :has() найдёт их по наличию метки.

.card {
  border: 1px solid #e5e7eb;
}

.card:has(.badge) {
  border-color: var(--accent);
  box-shadow: 0 6px 16px rgba(45, 108, 223, 0.15);
}

Обновите страницу: карточки с меткой «Хит» получили заметную акцентную рамку и лёгкую подсветку, а обычные остались нейтральными. Мы не добавляли этим карточкам никакого специального класса — CSS сам отличил их по содержимому. Стоит убрать метку из карточки, и выделение исчезнет автоматически. Это чистое, декларативное решение: вид зависит от содержимого, и всё описано в CSS.

Частые ошибки

  • Ждать выбор потомка.
    :has() стилизует внешний элемент, а не то, что внутри скобок; условие — это содержимое, а цель — сам элемент.
  • Лишний пробел.
    Псевдокласс присоединяют к элементу слитно: .card:has(.badge), а не .card :has(...).
  • Слишком тяжёлые условия.
    Очень сложные :has() на больших списках могут подтормаживать; держите условия простыми.
  • Считать, что нужен JavaScript.
    Многие задачи «стилизовать по содержимому» теперь решает :has() — прежде чем писать скрипт, проверьте, не хватит ли селектора.

Итоги

Прекрасная работа — вы освоили один из самых долгожданных селекторов CSS! Теперь вы умеете выбирать элемент по его содержимому через :has(), применять стиль к родителю по наличию нужного потомка, комбинировать :has() с классами и состояниями и решать задачи, которые раньше требовали JavaScript. В сквозном проекте вы выделили популярные карточки по наличию метки — без единого нового класса.

Отличный прогресс — ваш CSS стал по-настоящему «умным»! В следующем уроке мы разберём функцию calc(), которая позволяет вычислять значения прямо в стилях — складывать, вычитать и комбинировать разные единицы.

На следующий урок