Проблема: экспоненциальный рост селекторов
При работе с вложенными списками, которые могут быть как маркированными (<ul>), так и нумерованными (<ol>), часто возникает необходимость стилизовать маркеры на разных уровнях вложенности. Если такие списки находятся в нескольких независимых контейнерах сайта, количество возможных комбинаций быстро растёт.
Классический подход — перечислить каждую комбинацию в отдельном селекторе:
.box__description ul ul ul > li::before,
.box__context ul ul ul > li::before,
.box__overflow-content ul ul ul > li::before,
.box__description ol ul ul > li::before,
.box__context ol ul ul > li::before,
.box__overflow-content ol ul ul > li::before,
.box__description ul ol ul > li::before,
.box__context ul ol ul > li::before,
.box__overflow-content ul ol ul > li::before,
.box__description ol ol ul > li::before,
.box__context ol ol ul > li::before,
.box__overflow-content ol ol ul > li::before {
/* стили маркера */
}
Тут сразу 12 отдельных правил, каждое из которых повторяет одну и ту же декларацию. При добавлении нового контейнера или типа списка придётся вручную расширять список, что приводит к «комбинаторному взрыву». Такой код трудно читать, отлаживать и поддерживать: любой небольший сдвиг в структуре разметки может потребовать пересмотра десятков строк.
Решение: псевдокласс :is()
Современный CSS вводит псевдокласс :is(), который позволяет группировать несколько селекторов в один «мультипликатор». Синтаксис выглядит так:
:is(селектор1, селектор2, …) {
/* общие свойства */
}
Внутри :is() можно перечислять любые валидные селекторы, а сам псевдокласс будет вести себя как их объединение. Это открывает возможность заменить длинные цепочки «перечисли‑все‑варианты» на компактную структуру.
Применив :is() к задаче со списками, получаем:
:is(.box__description, .box__context, .box__overflow-content)
:is(ul, ol)
:is(ul, ol)
ul > li::before {
/* стили маркера */
}
Здесь:
:is(.box__description, .box__context, .box__overflow-content)охватывает все три контейнера.- Два подряд идущих
:is(ul, ol)описывают две вложенные уровни, каждый из которых может быть либо<ul>, либо<ol>. - Финальный
ul > li::beforeзадаёт стили для маркера самого вложенного списка.
Эта запись покрывает все 12 комбинаций, но состоит из одной строки кода. При необходимости добавить ещё один контейнер достаточно лишь добавить его в первый :is(), а новые типы списков — в соответствующий блок.
Практические выгоды
-
Сокращение объёма кода
При переходе от «брутфорса» к:is()количество строк уменьшилось примерно на 80 %. Меньше кода — меньше места для ошибок и конфликтов. -
Улучшенная читаемость
Структура селектора сразу показывает логику: «для этих контейнеров → для любых двух уровней списков → стилизуем маркер». Это упрощает восприятие даже для разработчиков, которые впервые видят файл стилей. -
Гибкость расширения
Добавление новых контейнеров, уровней вложенности или типов списков требует единственного изменения в соответствующей группе:is(). Нет необходимости копировать и вставлять длинные цепочки. -
Оптимизация расчётов браузером
По спецификации:is()не влияет на специфичность, а также позволяет движку CSS быстрее находить совпадения, поскольку селектор обрабатывается как один блок.
Ограничения и рекомендации
-
Поддержка браузеров
Псевдокласс:is()поддерживается всеми современными браузерами (Chrome 88+, Firefox 78+, Safari 14+, Edge 88+). При работе с устаревшими версиями следует предусмотреть полифилы или альтернативные стили. -
Не использовать в сочетании с
:where()для изменения специфичности
Если требуется снизить специфичность, лучше применять:where(), который работает аналогично, но имеет нулевую специфичность. В большинстве случаев:is()подходит, поскольку сохраняет оригинальную специфичность селекторов. -
Избегать вложенных
:is()без необходимости
Чрезмерное вложение может сделать селектор тяжело читаемым. Стремитесь к балансу между компактностью и ясностью. -
Тестировать на реальных примерах
При сложных и динамических структурах DOM рекомендуется проверять, как:is()взаимодействует с другими селекторами, особенно с псевдоклассами вроде:nth-childили:not().
Применение в реальном проекте
В проекте, где требуется стилизовать маркеры вложенных списков в трёх разных секциях сайта, переход к :is() позволил не только сократить количество строк, но и ускорить процесс разработки новых компонентов. При добавлении четвертой секции разработчик просто внес одну правку в первую группу :is(), и стили сразу начали работать без дополнительных правок.
Благодаря этому подходу команда смогла сосредоточиться на более значимых задачах — улучшении пользовательского опыта и оптимизации производительности, а не на «вычислении» всех возможных комбинаций селекторов вручную.