Нравится фронтенд — попробуйте самые подробные интерактивные тренажёры от HTML Academy бесплатно. Подойдут и действующим разработчикам, чтобы подтянуть навыки вёрстки.
Вступление
За годы работы с HTML, CSS и немного JavaScript я замечаю одну приятную закономерность: ВЭБ развивается. Причем развивается очень быстро. Для некоторых действий раньше приходилось писать много кода для построения сеток, но пришли Flex и Grid. Для стилизации некоторых блоков приходилось добавлять лишние классы, но пришли nth- селекторы. Но чего-то не хватало. Всё равно встречались моменты когда необходимо было добавлять лишние классы и приходилось с этим работать. И вот приходит декабрь 2023 год, затем январь 2024 года и 90% браузеров поддерживают новый псевдокласс :has().
Первый пример использования :has()
Что это такое и с чем его едят?
Рассмотрим следующий пример:
HTML
<label for="email" class="email-label">
<span class="visually-hidden">Введите email</span>
<input type="email" name="email" id="email" placeholder="Email" class="email-input">
</label>
<label for="email-1" class="email-label">
<span class="visually-hidden">Введите email</span>
<input type="email" name="email" id="email-1" placeholder="Email" class="email-input" value="email">
</label>
<label for="email-2" class="email-label">
<span class="visually-hidden">Введите email</span>
<input type="email" name="email" id="email-2" placeholder="Email" class="email-input" disabled>
</label>CSS
.visually-hidden {
position: absolute !important;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
clip: rect(1px, 1px, 1px, 1px);
padding: 0 !important;
border: 0 !important;
height: 1px !important;
width: 1px !important;
overflow: hidden;
}
.email-label {
position: relative;
}
.email-label::after {
content: '';
position: absolute;
right: 5px;
top: 50%;
transform: translateY(-50%);
width: 35px;
height: 35px;
mask-image: url(../img/email.svg);
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
background-color: currentColor;
}
.email-input {
display: block;
margin: 0;
padding: 4px;
padding-right: 40px;
font-size: 30px;
line-height: 1;
border: 3px solid #000000;
border-radius: 4px;
}
.email-input:hover {
border: 3px solid#0039a6;
}
.email-label:hover {
color: #0039a6;
}
.email-input:focus {
outline-offset: 3px;
}
.email-input:invalid {
border-color: #d52b1e;
}
.email-input:disabled {
border-color: #747474;
}Вроде бы всё предусмотрено, но есть неприятные моменты.

input в состояниях :invlid и :disabled иконки "tmail" черного цвета, а обводка input красного и серого.Если при наведении на элемент можно с помощью чистого CSS изменить цвет иконки, то в состояниях :invalid и :disabled нужно было динамически добавлять класс для label. Но с помощью с помощью псевдокласса :has() этот вопрос решается чистым CSS:
CSS
.email-label:has(.email-input:invalid) {
color: #d52b1e;
}
.email-label:has(.email-input:disabled) {
color: #747474;
}Не правда ли здорово? Теперь нет необходимости писать JavaScript для отслеживания состояний input.

Второй пример.
Это не единственный пример использовать данный псевдокласс. Допустим, пишем какую-то длинную статью с множеством разделов.
HTML
<div class="container-1">
<section>
<h2>Заголовок</h2>
<p>Какой-то текст</p>
</section>
<section>
<h2>Заголовок</h2>
<img src="#" alt="Описание картинки">
</section>
</div>CSS
.container-1 {
display: grid;
grid-template-columns: 1fr 1fr;
}
.container-1 h2 {
margin-top: 0;
}А по макету если после заголовков идёт тег <p>, то этому заголовку нужно обнулить нижний отступ. Опять же нас выручит псевдокласс :has()
CSS
h2:has(+ p) {
margin-bottom: 0;
}
<p> нет нижнего отступа.Этот псевдокласс просто манна небесная. Позволяет творить чудеса.
Третий пример :has() + :is().
Браузеры добавляют ссылкам подчёркивание, но иногда это подчёркивание мешает.

Обычно подчёркивание оставляют, для того чтобы было понятно что это ссылка, но если нужно в некоторых блоках его убрать? Например в <header> и <footer>, где списки ссылок и подчеркивание можно убрать. И так понятно что это ссылка. При помощи одного только :has() нам придётся добавить два селектора с одним правилом:
HTML
<header>
<a href="#">
<img src="#" alt="Описание картинки">
</a>
</header>
<main>
<a href="#">
<img src="#" alt="Описание картинки">
</a>
</main>
<footer>
<a href="#">
<img src="#" alt="Описание картинки">
</a>
</footer>CSS
header a:has(> img),
footer a:has(> img) {
text-decoration: none;
}
Можно ли написать это правило одним селектором? Да, и поможет нам псевдокласс :is():
CSS
:is(header, footer) a:has(> img) {
text-decoration: none;
}На первый взгляд ну что тут такого, один или два селектора? Сейчас этот один селектор быстрее читается чем два. Но если встретится проект в котором нужно написать кучу таких селекторов, то он бесспорно поможет в написании кода.
Предыдущий элемент.
HTML
<div class="block">1</div>
<div class="block center">center</div>
<div class="block">2</div>CSS
.block {
padding: 5px;
}
*:has(+ .center) {
background-color: #ff6347;
}
.center окрашен в томатный цвет.Специфичность.
Важно не забывать про специфичность селекторов. И в данном случае всё просто: рассмотренные псевдоклассы не имеют никакого веса. Однако вес создают теги, классы и другие псевдоклассы, которые и нужно учитывать.
Важное дополнение.
- Не стоит создавать селекторы с этим псевдоклассом и псевдоэлементами, судя по спецификации работать не будет.
- На MDN сказано что такие селекторы не будут работать в JavaScript.
Итоги
На момент написания статьи поддержка :has() и :is() хорошая, но опять же стоит учитывать аудиторию проекта, в том числе и государства для которых пишите проект.

Если для России можно использовать такие подходы, то для ряда восточных/азиатских стран лучше не рисковать. Но нужно смотреть какие именно стили добавляются с помощью этих свойств. Из моих примеров с инпутами или подчеркивания ссылок такой приём можно использовать везде. Так как вёрстка в целом не ломается.
