Перейти к содержанию

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

Определение

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

4 мин 40 Обновлено 12 августа

Специфичность (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 равны. Именно поэтому порядок подключения стилей в проекте имеет значение.

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

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 года.

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

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

Статья Что боты находят на сайте, а вы – нет Боты ходят по изнанке сайта, куда живой человек не забредёт, и фиксируют в логах то, чего не видно ни в Метрике, ни на витрине: падающие с ошибкой 500 страницы каталога, битый файл с 747 обращениями, поток сканеров секретов. Разбираю на реальных логах, что искать и как чинить. 7 мин 216 22 июля 2026 Инструкция Как выгрузить и читать access-логи, если хостинг хранит их три дня Access-лог – единственное место, где виден весь трафик сайта, включая ИИ-ботов, которых не замечают счётчики. Разбираю на практике: где взять логи, как не потерять их из-за ротации, какие команды показывают ботов, ошибки и подозрительную активность. 7 мин 170 21 июля 2026 Флагманский гайд AI-анализ email-обращений: методика на стыке Яндекс.Метрики и Claude Code Классический email-трекинг отвечает на вопрос «откуда пришло обращение». AI-слой отвечает на вопросы, которые раньше требовали ручного аналитика: значим ли email на фоне звонков, форм и мессенджеров, почему один источник даёт качественные обращения, а другой – пустые, какие сегменты визитов предшествуют письму, что написать в ответ с учётом пути пользователя. Здесь – как я собираю это на уже имеющемся стеке: Logs API Метрики, PostgreSQL и Claude Code, без новых платных сервисов. 11 мин 297 26 мая 2026 Флагманский гайд Майкор: ИИ-аудит проекта по 4 точкам контакта Майкор – это перекрёстный ИИ-аудит проекта в 4 точках контакта: сайт, контекстная реклама, AI-поиск и Яндекс.Карты. Я анализирую каждую систему и смотрю связки между ними – где маркетинг рассказывает одно, реклама ведёт на другое, а AI-системы цитируют третий номер телефона. В одном из моих аудитов медицинской клиники в Москве у организации в индексе AI-сервисов оказалось 6 разных номеров и всего 7 упоминаний на 64 проверочных запроса – при том, что в обычной выдаче Яндекса клиника была в топ-1. Майкор закрывает такие разрывы за один аудит вместо четырёх раздельных. 16 мин 501 13 мая 2026 Гайд FAQPage Schema: как разметить блок частых вопросов и попасть в AI-ответы FAQPage schema – один из самых эффективных типов разметки для попадания в AI-ответы. Страницы с FAQPage schema цитируются AI-поисковиками в 2,7 раза чаще. В этом гайде – формат разметки, правила написания ответов для AI, реализация на 1С-Битрикс и типичные ошибки, которые обнуляют эффект. 8 мин 331 25 апреля 2026