CSS-селектор :has() для родительского элемента

Как назначить CSS-свойства селектору-родителю, в зависимости от существования элемента внутри него и управлять свойствами соседних HTML-элементов? Новый CSS-селектор :has() поможет добраться до родителя определенного элемента с помощью только CSS. Например, если внутри контейнера есть изображение, этому контейнеру нужно добавить display: flex.

Суть проблемы: задать стиль контейнеру-родителю в зависимости от существования элемента внутри него. Раньше это можно было сделать только с помощью Javascript или на уровне динамического создания шаблона. Т.е. создавать лишние CSS-классы и переключать их в зависимости от содержимого. Например:

проблема до появления CSS-селектора :has
Карточки с изображением и описанием, и аналогичная без изображения

В CSS для такого можно сделать что-то вроде:

/* Карточка с изображением */
.card {
    display: flex;
    align-items: center;
    gap: 1rem;
}

/* Карточка без изображения */
.card--plain {
    display: block;
    border-top: 3px solid #7c93e9;
}

И в HTML:

<!-- Карточка с изображением -->
<div class="card">
    <div class="card__image">
        <img src="awameh.jpg" alt="">
    </div>
    <div class="card__content">
        <!-- Текстовое описание -->
    </div>
</div>

<!-- Карточка без изображения -->
<div class="card card--plain">
    <div class="card__content">
        <!-- Текстовое описание -->
    </div>
</div>

Таким образом, чтобы поправить внешний вид карточкам без изображения, потребуется контейнеру-родителю .card добавлять ещё CSS-класс .card--plain.

Решить проблему с избыточными CSS-классами можно с помощью CSS-селектора :has():

.card:has(.card__image) {
    display: flex;
    align-items: center;
}

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

See this code CSS-селектор :has on x.xhtml.ru.

.card:has(h2 + p) {...}

Стиль будет применяться к контейнеру .card при условии, когда внутри него за <H2> будет следовать <P>. Но не будет применяться, когда между <H2> и <P> появится какой-то другой (<HR> в первой или <INPUT> в последней карточках) элемент.

.card:has(h2 + input:focus) {...}

Стиль будет применяться к контейнеру .card при условии, когда внутри него за <H2> не только будет следовать <INPUT>, но в это поле ещё будет помещён курсор (фокус в поле).

Поддержка CSS-селектора :has() вашим браузером

Примеры использования CSS-селектора :has()

Наиболее интересно применение CSS-селектора :has() при работе с формами и состояниями элементов управления в них. Например, можно показывать сообщения и кнопки в произвольном месте контейнера с формой.

Форма компонента фильтра

В этом примере есть форма-фильтр с несколькими параметрами. Когда ни один из них не отмечен, кнопка сброса отсутствует. Однако, когда хотя бы один отмечен, эту кнопку нужно показать.

Использование CSS-селектора :has для отображения элементов в форме
Использование CSS-селектора :has для отображения кнопки в форме

Это не трудно сделать с помощью CSS-селектора :has:

.btn-reset {
    display: none;
}

.multiselect:has(input:checked) .btn-reset {
    display: block;
}

See this code Reset button visibility with CSS :has on x.xhtml.ru.

Показывать/скрывать элементы формы по условию

Показать поле формы в зависимости от выбранной опции с помощью :has()
Показать поле формы в зависимости от выбранной опции с помощью :has()

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

.other-field {
    display: block;
}

form:has(option[value="other"]:checked) .other-field {
    display: block;
}

При этом нет никакой необходимости волноваться об исходном порядке следования элементов в HTML.

Информационные модули

Когда в тестовом поле ввода есть ошибка, может понадобиться, чтобы заголовок контейнера тоже изменился и указывал на эту ошибку.

Изменение цвета заголовка формы при ошибке в поле с помощью :has()
Изменение цвета заголовка формы при ошибке в поле с помощью :has()
.module:has(.input-error) .headline {
    color: #ca3131;
}

Элемент навигации с подменю

Стрелка в панели навигации для пункта, у которого есть подменю
Стрелка в панели навигации для пункта, у которого есть подменю

В этом примере показывается, как скрыть стрелку в зависимости от того, есть подменю или нет. Это тоже можно легко сделать с помощью CSS-селектора :has. Достаточно проверить, содержится ли внутри <li> элемент списка <ul>. И если да – содержится, надо показать значок стрелки.

See this code Submenu arrow indicator with CSS :has on x.xhtml.ru.

За стрелку у пунктов с подменю здесь отвечает:

li:has(ul) > a:after {
    content: "";
    /* стили для стрелки */
}

Без CSS-селектора :has, потребовался бы класс для <li> с подменю и способ управлять им. Что-то вроде такого:

.nav-item--with-sub > a:after {
    content: "";
    /* стили для стрелки */
}

Переключение цветовых схем

Пример использования CSS-селектора :has() для изменения цветовой схемы веб-сайта. Если есть несколько тем, созданных с помощью CSS-переменных, ими можно управлять с помощью выпадающего списка <select>.

html {
    --color-1: #9e7ec8;
    --color-2: #f1dceb;
}
цветовая схема по умолчанию
Цветовая схема по умолчанию

И если выбрать другой вариант из списка, в зависимости от выбранной опции CSS-переменные можно изменить так:

html:has(option[value="blueish"]:checked) {
    --color-1: #9e7ec8;
    --color-2: #f1dceb;
}
цветовая схема изменена с помощью :has() и CSS-переменных
Цветовая схема изменена с помощью :has() и CSS-переменных

See this code Choose color scheme with CSS :has on x.xhtml.ru.

Изменение сетки в зависимости от количества элементов

С CSS-grid можно использовать функцию minmax() для создания действительно отзывчивых элементов сетки с автоматическим изменением размера. Однако этого может быть недостаточно. Например, нужно изменять сетку в зависимости от количества элементов.

Сетка из 4-х элементов
Сетка из 4-х элементов
.wrapper {
    --item-size: 200px;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(var(--item-size), 1fr));
    gap: 1rem;
}

Если появится пятый элемент, он будет перенесен в новую строку.

Сетка из 5 элементов с переносом
Сетка из 5 элементов с переносом

От переноса пятого элемента на новую строку легко избавиться, если проверить количество элементов внутри .wrapper. Опять же, здесь используется концепция количественных запросов.

.wrapper:has(.item:nth-last-child(n + 5)) {
    --item-size: 120px;
}
Сетка из 5 элементов без переноса
Сетка из 5 элементов без переноса

Изображение с подписью

Изображения с подписью и без
Изображения с подписью и без

В этом примере используется HTML-элемент <figure>. Если внутри него есть <figcaption>, стиль картинки должен немного отличаться:

  • Добавится белый фон
  • Немного обводки
  • Уменьшатся изображение и border-radius
figure:has(figcaption) {
    padding: 0.5rem;
    background-color: #fff;
    box-shadow: 0 3px 10px 0 rgba(#000, 0.1);
    border-radius: 3px;
}

Заключение

Использование CSS-селектора :has() позволяет во многих случаях, только с помощью CSS, легко и просто сделать вещи, которые требовали Javasript и динамическое добавление лишних CSS-классов для изменения стилей за пределами элемента-инициатора этих изменений.