Селектор :has() только добавляется в CSS, поэтому некоторыми браузерами на момент написания статьи он может не поддерживаться в полной мере. Он сильно упрощает разработку сайтов, так как позволяет задавать условия, при которых будет применяться тот или иной стиль. Это делает CSS более вариативным, уменьшает вероятность ошибки, а также время на написание кода и его размер. Далее рассмотрим особенности работы и применения селектора :has().

Для чего нужен селектор :has() в CSS

Для более удобной стилизации родительских элементов, посредствам задания условий. Например, если внутри указанного элемента есть другой компоненты или компоненты, то будут применены одни стили. Если данного элемента нет, то ничего применяться дополнительно не будет.

Рассмотрим самый простой пример:

div:has(p) {
   background: red;
}

Согласно этому условию компонент div получит красный фон в том случае, если внутри него есть хоть один абзац, обернутый в тег <p>. Такой подход позволяет решать больший спектр задач по стилизации родительских элементов, делает верстку более вариативной, следовательно, сайт будет проще администрировать в будущем.

Перейдем к конкретному примеру – у нас есть два варианта оформления карточки для аннотации статьи: одна подразумевает изображение, а другая нет.

Распространенный пример с карточками с картинкой и без из документации к CSS :has()

В классическом CSS требуется задать стили и для карточки с изображением, и без. Вот пример:

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

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

Специально для карточки с изображением был создан отдельный класс. Нужно не забывать в HTML проставлять нужные классы для карточек – это дополнительная работа. Еще такой подход повышает вероятность ошибки – если забыли поставить нужный класс, а внутри карточки есть картинка, то к ней будут применены неверные стили.

Селектор has() позволяет проверить, есть ли картинка внутри карточки и если да, то применить к ней соответствующие стили:

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

Вместо написания классов, дополнительных блоков CSS вся запись буквально сократилась до одного условия: если в блоке div с классом card есть элемент с классом card__image, то изменить отображение с block на flex, а элементы выровнять по центру. Если указанного компонента внутри блока card нет, то значения стилей остаются по умолчанию.

Активное использование has() селекторов при написании CSS стилей позволит не только упростить процесс разработки, но и сделать более удобным администрирование, оптимизацию шаблона под какой-либо движок. Теперь вместо прописывания нескольких блоков стилей в CSS и сложной вложенной системы классов в HTML достаточно только задать одно условие в коде.

Другие сценарии использования

Функционал has() селектора позволяет не только проверить содержание внутри родителя тех или иных элементов, но и их следование друг за другом. Причем, для этого у них не обязательно должен быть общий родитель или вообще какой-либо родитель.

Вот пример:

.card h2:has(+ p) { }

Здесь идет проверка, следует ли тег <p> за тегом <h2> внутри элементов с классом card. Таким образом становится проще делать разные стилистические оформления внутри родителя. Например, если после заголовка идет какой-то текст, обернутый в тег <p>, то размер и позиционирование заголовка меняется, иначе остаются значения по умолчанию.

Еще вариант применения – проверка состояния кнопок и полей, например, внутри форм:

form:has(input:focused) {
   background-color: lightgrey;
}

Если внутри формы имеется хотя бы один input с классом focused, то фоновый цвет всей формы (она в данном случае является родителем) меняется на светло-серый. Такие условия можно прописывать к кнопкам, например, чтобы при нажатии или наведения на нее менялся не только внешний вид кнопки, но и блока, внутри которого она располагается.

Специфика селектора has() в CSS

По своему принципу работы он похож на многие другие псевдоклассы в CSS, однако у него есть существенные отличия – аргумент должен начинаться с комбинатора, включать в себя другие псевдоклассы. Рассмотрим особенности has() более углубленно.

Области видимости

Селектор has() является относительным селектором – это значит, что у его аргумента всегда должен быть какой-нибудь комбинатор. Даже если вы его не прописываете, например, в .card:has(.card__image) это не значит, что его нет. Подразумевается, что перед аргументом стоит пробел, который является комбинатором потомственного элемента внутри родительского.

Для наглядности сравним два селектора: селектор aside a:is(section a) и относительный aside:has(section a). Они выглядят похоже, да и выполняют похожие задачи, но вот смысл у них совершенно разный. В первом случае происходит поиск вообще всех ссылок, у которых общим предком является aside и section. При этом порядок следования абсолютно неважен. Например, ссылка может быть в примечании к aside и где-нибудь в section. А вот селектор aside:has(section a) ведет проверку ссылок только внутри section, которые вложены в aside. При этом порядок важен.

Допустимо содержание незнакомых селекторов в аргументе

Эту особенность разработчики называют “прощающей” – если внутри аргумента оказался один некорректный селектор, например, с ошибкой в написании или просто несуществующий в документе или внутри выбранного блока, то код все равно сработает корректно. Правда, он применится только к тем селекторам, которые прописаны корректно.

Правда, основное преимущество этой особенности немного в другом – она позволяет игнорировать неподдерживаемые селекторы в аргументах. Это значит, что все будет работать корректно, даже если какой-то аргумент не поддерживается одним браузером, но поддерживается при этом другим.

Про поддержку в браузерах и других средах

На момент написания статьи полноценная поддержка селектора has() реализована только в браузере Safari TP 137 и выше. Над внедрением работает и Google Chrome, но пока полноценной поддержки нет. Что касается остальных веб-обозревателей, то их разработчики пока ждут полноценной поддержки в Chrome, после чего будут пытаться реализовать ее на своих продуктах.

Список браузеров поддерживающий :has() CSS. Зеленым версии с полной поддержкой, с зелеными флажками ожидается поддержка в ближайшее время, красные без поддержки

Вообще, такой же метод has() уже давно есть в jQuery. Отвечает он примерно за то же, что и его аналог в CSS, но поддерживает выделение JS-элементов. Так как в jQuery поддержка уже давно реализована, то использование данного метода через скрипты поддерживается любыми браузерами. Полноценное внедрение селектора has() в CSS позволит облегчить процесс выделения и наследования – теперь не требуется писать для этой задачи отдельный скрипт и подключать его к HTML-документу.

Также метод has() поддерживается некоторыми PDF-рендерами для HTML. Если желаете потренироваться, то можете использовать онлайн-сервис PrintCSS.live. Правда, функционал все равно будет ограничен, например, не получится работать с формами.

Примеры использования has в CSS

Далее рассмотрим на конкретных примерах, где лучше использовать селектор has для определения наследования стилей, а не прописывать классы и стили для них вручную.

Оформление заголовков

Например, у нас есть два варианта заголовка – один с какой-нибудь якорной ссылкой, а другой без нее. Тот, у которого есть ссылка имеет дополнительное подчеркивание.

Пример заголовка с дополнительной ссылок и без нее

Вот так будет выглядеть его HTML-каркас:

<section>
  <div class="section-header">
   <h2>Latest articles</h2>
   <a href="/articles/>See all</a>
  </div>
</section>

Как видите, при использовании has() совсем необязательно проставлять отдельные классы для ссылок и заголовков – достаточно завернуть их в родительский контейнер, которым служит div с классом section-header.

Теперь перейдем непосредственно к оформлению в CSS:

.section-header {
   display: flex;
   justify-content: space-between;
}

.section-header:has(> a) {
   align-items: center;
   border-bottom: 1px solid;
   padding-bottom: 0.5rem;
}

Обратите внимание на аргумент, в котором прописан символ >. С его помощью выбирается только прямая дочерняя ссылка внутри родительского блока. В рассматриваемом примере вполне можно было обойтись без него, так как в блоке ссылка одна. Однако, если их будет несколько штук, то лучше использовать комбинатор >.

Пример с карточками

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

.card:not(:has(.card__image)) {
   border-top: 3px solid #7c93e9;
}

После класса card мы использовали условие not, то есть, если в блоке card нет другого блока с классом card__image, то к нему будут применяться указанные стили. В нашем случае это синяя граница.

Если не использовать has, то пришлось бы в HTML прописывать дополнительные классы и уже для них писать стили, например, вот так:

.card--default {
   display: flex;
   align-items: center;
}

.card--plain {
   border-top: 3px solid #7c93e9;
}

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

  • Много лишнего кода. Все это дублирование классов под разные вариации увеличивает количество кода. Если в случае с 1-2 компонентами увеличение незначительное, то когда их несколько десятков оно уже становится заметным.
  • Чем больше кода, тем в нем легче запутаться, особенно, когда используются похожие по названию классы. Писать комментарии для каждого блока тоже не очень удобно.
  • Иногда код может быть просто нелогичным, особенно, если по какой-то причине была нарушена последовательность классов в CSS. Стили в этом случае все равно корректно применяться, но вот человеку будет сложно вносить изменения в CSS-код, так как некоторые блоки в нем будут вразнобой.

Еще карточки

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

Еще пример с карточками

Оформим для примера HTML-структуру карточки, где в нижней части несколько действий:

<div class="card">
  <div class="card__thumb><img src="cool.jpg"/></div>
  <div class="card__content">
    <div class="card__actions">
      <div class="start">
        <a href="#">Like</a>
        <a href="#">Save</a>
      </div>
      <div class="end">
         <a href="#">More</a>
      </div>
    </div>
   </div>
</div>

Здесь есть два ключевых класса: start и end, в которых содержатся элементы управления карточкой. Они и будут использованы в качестве аргументов has:

.card__actions:has(.start, .end) {
   display: flex;
   justify-content: space-between;
}

Компоненты фильтрации

Это также можно применять к различным формам опроса. У нас есть форма фильтрации/опросник: когда никакой вариант не отмечен, то кнопки сброса или отправки нет, а когда отмечается хотя бы один из пунктов эта кнопка появляется. Пример показан на скриншоте.

Пример фильтров с появляющейся кнопкой сброса

Вот как это можно реализовать с помощью has:

.btn-reset {
   display: none;
}

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

По умолчанию ставим в стилях кнопки скрытое отображение. Однако, если в родительском классе появляется хотя бы один input в выбранном положении, то кнопка становится видимой.

Про возможные баги

Так как селектор has в CSS пока находится в экспериментальном состоянии, то без багов не обошлось. Они касаются динамических псевдоклассов, изменениями DOM. Они могут просто не работать, хотя все настроено в соответствии с инструкциями и рекомендациями из свежей версии документации. На подобное поступали множественные жалобы разработчиков. Проблему обещают исправить в следующих редакциях, а пока приходится пользоваться “костылями”. Все же стоит понимать, что селектор has только начинает внедряться в стандарт CSS и поддерживается далеко не всеми браузерами, поэтому проблемы в работе вполне могут возникать.

Заключение

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