Специфичность селектора

Определение

Специфичность селектора – механизм каскада CSS, определяющий, какое из конфликтующих правил применит браузер. Считается триадой A-B-C (ID, классы, теги) и сравнивается попозиционно: один ID тяжелее любого числа классов.

4 мин 112 Обновлено 15 октября

Специфичность (specificity) – механизм каскада CSS: когда к одному элементу подходят несколько правил с одним и тем же свойством, браузер сравнивает вес их селекторов и применяет самое специфичное. Понятие существует со времён первых версий CSS, а актуальные правила подсчёта – включая вес функциональных псевдоклассов – закреплены в спецификации Selectors Level 4. Понимание специфичности снимает главную боль вёрстки – "стиль прописан в файле, но элемент его игнорирует".

Как это работает

Специфичность – не одно число, а триада из трёх счётчиков A-B-C. Браузер раскладывает каждую часть селектора в одну из трёх колонок и записывает результат вида 1-2-1.

Триада специфичности A-B-C
КолонкаЧто считаетсяПримерыВклад
AID-селекторы#menu, #app1-0-0
BКлассы, селекторы атрибутов, псевдоклассы.card, [type=radio], :hover0-1-0
CСелекторы типов (тегов) и псевдоэлементыp, ::before0-0-1

Например, :root #app input:required – это 1-2-1: один ID, два псевдокласса и один тег. Порядок частей внутри селектора не важен: .class#id и #id.class дают одинаковые 1-1-0.

Триада и "баллы 100/10/1" – в чём разница

В русскоязычных статьях до сих пор гуляет устаревшая модель: "ID – 100 баллов, класс – 10, тег – 1; складываем и сравниваем суммы". MDN и web.dev прямо предостерегают от неё. Специфичность – не десятичное число, а тройка, которая сравнивается попозиционно, слева направо: сначала колонка A, при равенстве – B, затем C. Переноса "разрядов" между колонками не существует: один ID тяжелее любого количества классов.

Почему модель "баллов" даёт неверный ответ
СелекторТриада"Баллы" (устаревшая модель)Итог по триаде
#promo1-0-0100Побеждает: колонка A старше
Цепочка из 11 классов0-11-0"110"Проигрывает, хотя "баллов" больше
.a .b .c [id=el]0-4-0"40"Проигрывает #el (1-0-0)

Что не влияет на вес

  • Универсальный селектор * – даёт 0-0-0.
  • Комбинаторы (пробел, >, +, ~) – не считаются: div > p и div p равны, 0-0-2.
  • :where() – всегда 0-0-0 независимо от аргументов.
  • Наследование и близость в DOM – унаследованное значение всегда проигрывает прямому таргетированию, а body h1 и html h1 равны: "близость" предка не учитывается.

Инлайн-стили (атрибут style) и !important – тоже не специфичность, а другие уровни каскада, которые оцениваются до её сравнения: инлайн-стиль перебивает любой селектор, а !important перекидывает объявление в отдельный приоритетный слой. При конфликте двух !important побеждает уже более специфичный селектор. MDN называет !important антипаттерном; для управляемых оверрайдов рекомендованы каскадные слои @layer: при конфликте правил из разных слоёв специфичность вообще не сравнивается, роль играет порядок слоёв.

Функциональные псевдоклассы :is(), :not() и :has() собственного веса не имеют, но получают специфичность самого специфичного селектора из своих аргументов: :is(p, #fakeId) – 1-0-0, p:not(#fakeId) – 1-0-1. А :nth-child(An+B of S) даёт 0-1-0 за сам псевдокласс плюс вес самого специфичного селектора в списке S.

Зачем это нужно

Первая практическая задача – диагностика. Если стиль написан, но не применяется, чаще всего его перебивает более специфичное правило: DevTools показывают проигравшие объявления перечёркнутыми в панели Styles, а триаду селектора – в подсказке при наведении. Вторая – архитектура: чем ровнее специфичность по проекту, тем предсказуемее оверрайды и меньше соблазн тянуться за !important.

Управлять весом можно в обе стороны:

  • Снизить – записать ID как селектор атрибута: [id=x] даёт 0-1-0 вместо 1-0-0; обернуть селектор в :where() и обнулить вес – стандартный приём резетов и CSS-библиотек, чтобы пользовательские стили перебивали их одним классом.
  • Повысить без !important – продублировать ID (#id#id – 2-0-0) или добавить :not(#fakeId): "накачка" триады, оправданная как точечный оверрайд чужого кода.
  • Держать ровной – методологии вроде БЭМ строят стили на одиночных классах (0-1-0), исключая войны специфичности в командной работе.

Пример

* { }                         /* 0-0-0 */
p::first-line { }             /* 0-0-2: тег + псевдоэлемент */
[type=radio]:hover { }        /* 0-2-0 */
div.outer p { }               /* 0-1-2 */
:root #app input:required { } /* 1-2-1 */

/* Конфликт: побеждает ID, а не «больше частей» */
#el { color: green; }         /* 1-0-0 – применится */
.a .b .c [id=el] { color: red; } /* 0-4-0 */

/* :is / :not / :has – самый специфичный аргумент */
:is(p, #fakeId) { }           /* 1-0-0 */
h1:has(+ h2, > #fakeId) { }  /* 1-0-1 */

/* :where – всегда ноль */
:where(#theme) a { }          /* 0-0-1 */
footer a { }                  /* 0-0-2 – перебивает предыдущее */

Разберём конфликт из середины примера. Селектор .a .b .c [id=el] выглядит длиннее и "точнее", и по устаревшей модели баллов набрал бы 40 против 100 – но решает не длина, а старшая колонка: у #el есть ID, поэтому текст будет зелёным. В работе с Битрикс-шаблонами это типовая ситуация: стили ядра или готового шаблона компонента написаны через ID-обёртки, и перебить их одним классом не получится. Рабочие варианты – селектор атрибута [id=...] вместо #id в своём коде или осознанное повышение специфичности, но не !important, который через полгода придётся перебивать ещё одним !important.

Частые вопросы

Как посчитать специфичность селектора вручную?

Разложите селектор в три счётчика A-B-C: A – ID-селекторы (#id), B – классы, селекторы атрибутов и псевдоклассы (.card, [type=radio], :hover), C – теги и псевдоэлементы (p, ::before). Например, :root #app input:required – это 1-2-1. Универсальный селектор * и комбинаторы не считаются вовсе.

Правда ли, что специфичность – это сумма баллов 100, 10 и 1?

Нет, это устаревшее упрощение из старых статей – MDN и web.dev прямо предостерегают от него. Триада сравнивается попозиционно: сначала счётчик ID, при равенстве – классы, затем теги. Переноса "разрядов" нет: селектор из одиннадцати классов (0-11-0) проиграет одному ID (1-0-0), хотя "по баллам" набрал бы 110 против 100.

Что не влияет на специфичность селектора?

Универсальный селектор * даёт 0-0-0, комбинаторы (пробел, дочерний, соседние) не считаются вовсе, а :where() всегда равен нулю независимо от аргументов. Инлайн-стили и !important – тоже не специфичность, а отдельные, более приоритетные уровни каскада. Близость в DOM не учитывается: body h1 и html h1 равны.

Почему !important перебивает даже ID-селектор и почему это антипаттерн?

!important перекидывает объявление в отдельный, более приоритетный слой каскада, который оценивается до сравнения специфичности. MDN называет его антипаттерном: перебить такой стиль можно только ещё одним !important, и проект скатывается в войну приоритетов. Для управляемых оверрайдов рекомендованы каскадные слои @layer.

Какая специфичность у :is(), :not() и :has()?

Сам псевдокласс веса не добавляет, но правило получает специфичность самого специфичного селектора из его аргументов. Так, :is(p, #fakeId) считается как 1-0-0, а p:not(#fakeId) – как 1-0-1. Это правило закреплено в спецификации Selectors Level 4.

Чем :where() отличается от :is()?

Только специфичностью: :is() берёт вес самого специфичного аргумента, а :where() всегда даёт 0-0-0. Поэтому :where() используют в резетах и CSS-библиотеках – их стили легко переопределить одним классом, без борьбы за приоритет.

Что побеждает при полностью равной специфичности?

Последнее правило в порядке исходного кода: браузер применит то, что объявлено ниже – в том же файле или в файле, подключённом позже. Порядок частей внутри селектора при этом не важен: .class#id и #id.class равны. Именно поэтому порядок подключения стилей в проекте имеет значение.

112

Связанные термины

Selectors Level 4 Selectors Level 4 – модуль спецификации CSS от W3C, расширивший селекторы: :is(), :where(), :has(), :not() со списком, :nth-child(An+B of S). Статус – Working Draft (редакция 22.01.2026), но ядро давно поддержано всеми браузерами. Псевдокласс :has() :has() – реляционный CSS-псевдокласс из Selectors Level 4: выбирает элемент по наличию потомка, соседа или состояния внутри. Долгожданный "родительский селектор" работает во всех браузерах с декабря 2023 (Firefox 121). Псевдокласс :not() :not() – CSS-псевдокласс отрицания: выбирает элементы, не соответствующие селектору-аргументу. В Selectors Level 4 принимает список селекторов (:not(.a, .b)) – кроссбраузерно с января 2021 (Chrome 88, Firefox 78, Safari 9). Псевдокласс :nth-child() :nth-child() – CSS-псевдокласс, выбирающий элементы по позиции среди всех детей родителя: по номеру, ключевым словам odd/even или формуле An+B. Нумерация начинается с 1, а не с 0. Синтаксис of S Синтаксис of S – часть аргумента :nth-child() и :nth-last-child() из Selectors Level 4: браузер сначала отбирает детей по селектору S, а затем считает позиции только среди них. Во всех браузерах – с 2023 года.

Материалы по теме

Что почитать дальше по этой теме

Инструкция Уведомления о заявках с сайта в MAX: что нужно для подключения Что нужно, чтобы заявки с сайта приходили в MAX: верифицированная организация, модерация бота, токен из веб-версии и группа для заявок. Плюс разбор для разработчика. 7 мин 219 4 октября 2026 Статья Что боты находят на сайте, а вы – нет Боты ходят по изнанке сайта, куда живой человек не забредёт, и фиксируют в логах то, чего не видно ни в Метрике, ни на витрине: падающие с ошибкой 500 страницы каталога, битый файл с 747 обращениями, поток сканеров секретов. Разбираю на реальных логах, что искать и как чинить. 7 мин 436 22 июля 2026 Инструкция Как выгрузить и читать access-логи, если хостинг хранит их три дня Access-лог (журнал обращений к серверу) – единственное место, где виден весь трафик сайта, включая ИИ-ботов, которых не замечают счётчики. Разбираю на практике: где взять логи, как не потерять их из-за ротации, какие команды показывают ботов, ошибки и подозрительную активность. 7 мин 431 21 июля 2026 Флагманский гайд ИИ-анализ email-обращений: методика на стыке Яндекс.Метрики и Claude Code Классический email-трекинг отвечает на вопрос "откуда пришло обращение". ИИ-слой отвечает на вопросы, которые раньше требовали ручного аналитика: значим ли email на фоне звонков, форм и мессенджеров, почему один источник даёт качественные обращения, а другой – пустые, какие сегменты визитов предшествуют письму, что написать в ответ с учётом пути пользователя. Здесь – как я собираю это на уже имеющемся стеке: Logs API Метрики, PostgreSQL и Claude Code, без новых платных сервисов. 11 мин 458 26 мая 2026 Флагманский гайд Майкор: ИИ-аудит проекта по 4 точкам контакта Майкор – это перекрёстный ИИ-аудит проекта в 4 точках контакта: сайт, контекстная реклама, ИИ-поиск и Яндекс.Карты. Я анализирую каждую систему и смотрю связки между ними – где маркетинг рассказывает одно, реклама ведёт на другое, а ИИ-системы цитируют третий номер телефона. В одном из моих аудитов медицинской клиники в Москве у организации в индексе ИИ-сервисов оказалось 6 разных номеров и всего 7 упоминаний на 64 проверочных запроса – при том, что в обычной выдаче Яндекса клиника была в топ-1. Майкор закрывает такие разрывы за один аудит вместо четырёх раздельных. 16 мин 744 13 мая 2026