Skip to main content

Специфічність селекторів CSS

Специфічність CSS-селекторів - це система підрахунку балів, яку браузер використовує, щоб вирішити яке правило CSS перемагає, коли кілька правил націлені на один елемент.

Теорія

Коротко

  • Уяви аукціон: інлайн-стилі роблять ставку $1000, кожен ID - $100, кожен клас - $10, кожен тег - $1. Хто більше поставив, той виграв.
  • Рахунок записується як чотири числа (A,B,C,D): інлайн-стилі / ID / класи+атрибути+псевдокласи / елементи+псевдоелементи.
  • Якщо рахунки рівні, перемагає правило, оголошене пізніше в таблиці стилів.
  • !important обходить специфічність повністю і потрапляє в окремий шар пріоритетів.
  • У більшості випадків краще використовувати класи, а не ID. Їх простіше перевизначити.

Швидкий приклад

css
div { color: blue; } /* 0,0,0,1 - елемент */ .highlight { color: red; } /* 0,0,1,0 - клас, перемагає елемент */ #main .highlight { color: green; } /* 0,1,1,0 - ID+клас, перемагає клас */
html
<div id="main" class="highlight">Якого кольору цей текст?</div>

Текст буде зеленим. Селектор #main .highlight набирає (0,1,1,0) і перевершує обидва попередніх правила. Браузер порівнює колонки зліва направо. Перша колонка де числа різняться - вирішує.

Як рахується рахунок

Кожна частина селектора додає бал в одну з чотирьох колонок:

КатегоріяКолонкаПриклади
Інлайн-стиліA (1,0,0,0)style="color:red"
IDB (0,1,0,0)#header, #nav
Класи, атрибути, псевдокласиC (0,0,1,0).btn, [type="text"], :hover
Елементи і псевдоелементиD (0,0,0,1)div, p, ::before

Універсальний селектор * дає нуль балів. Комбінатори >, + і ~ теж дають нуль. Рахуються тільки фактичні компоненти.

Тому div > p.class = один тег + один тег + один клас = (0,0,1,2). А :nth-child(2n) - це псевдоклас, тому він дає (0,0,1,0), таку ж вагу як і звичайний клас.

З цієї ж причини [type="button"] і .btn мають однаковий рахунок. Атрибутні селектори знаходяться в тій самій колонці, що й класи.

Коли специфічність важлива

  • Ти додав правило з класом, але елемент досі показує стилі зі старого правила з ID. ID виграє, бо (0,1,0,0) перевершує (0,0,1,0).
  • Ти перевизначаєш компонентну бібліотеку. Комбінація .btn.btn-primary в Bootstrap дає (0,0,2,0). Один кастомний клас з (0,0,1,0) її не переб'є. Або збіжи глибину, або додай батьківський клас.
  • Стиль не застосовується і незрозуміло чому. Відкрий DevTools, перейди на вкладку Computed. Закреслені оголошення програли через вищу специфічність десь у CSS каскаді.
  • Ти будуєш систему тематизації. Ланцюжок класів .theme-dark .card дає (0,0,2,0) і залишається простішим для перевизначення, ніж будь-який ID-селектор.

Типові помилки

Помилка 1: вважати, що порядок у файлі завжди вирішує

css
.box { color: red; } /* 0,0,1,0 */ div.box { color: blue; } /* 0,0,1,1 - перемагає, хоча стоїть нижче */

Порядок оголошень є лише тайбрейкером коли рахунки специфічності рівні. Якщо рахунки різні, вищий рахунок перемагає незалежно від порядку.

Помилка 2: ланцюжки з ID

css
#header #nav #item { padding: 10px; } /* 0,3,0,0 */

Щоб перевизначити це правило, потрібно мінімум чотири класи в ланцюжку. BEM-класи на кшталт .header__nav-item дають (0,0,1,0) і залишаються простими для роботи.

Помилка 3: забути, що атрибутні селектори мають таку ж вагу, як класи

css
[type="button"] { border: 1px solid; } /* 0,0,1,0 - як .btn */ button.primary { border: 2px solid; } /* 0,0,2,0 - перемагає */

Багато розробників вважають атрибутні селектори слабшими за класи. Це не так.

Помилка 4: тягнутися до !important одразу

В проектах на Tailwind це призводить до реальних проблем з підтримкою. Щойно одне правило використовує !important, наступне перевизначення теж його потребує. Краще підняти легітимну специфічність: додай батьківський селектор або ще один клас.

Де зустрічається в реальному коді

  • Bootstrap: .btn-primary у комбінації дає (0,0,2,0) і перевершує .btn з (0,0,1,0). Кастомні теми мають збігатися з цим рівнем.
  • Material-UI: Перевизначай компоненти через #id.class або & .MuiButton-root в JSS, отримуючи (0,1,1,0) або (0,0,2,0).
  • Tailwind: Кожен утилітарний клас дає (0,0,1,0). Вони стекуються передбачувано, але конфлікти виникають коли поряд підключаються стилі компонентів.
  • Styled-components і CSS Modules: Генерують унікальні назви класів з хешем і повністю обходять конфлікти без жодних розрахунків специфічності.

Регулярно бачу в продакшені: розробник додає ID-селектор, щоб полагодити перевизначення, а через пів року ніхто вже не наважується чіпати той рядок. Тримайся класів.

Питання на співбесіді

Q: Яка специфічність у div > p.class?
A: (0,0,1,2). Один клас і два елементи. Комбінатор > не додає балів.

Q: Як !important вписується в специфічність?
A: Фактично ніяк. Браузери поміщають !important-правила в окремий пул вище звичайних правил. Всередині цього пулу специфічність все одно діє. !important автора перевершує звичайні правила, але !important користувача перевершує !important автора.

Q: Скільки балів дає *?
A: Нуль. (0,0,0,0). Комбінатори теж нуль. Рахуються тільки фактичні компоненти: ID, класи, елементи.

Q: Два правила мають однакову специфічність. Яке перемагає?
A: Те, яке оголошено пізніше в таблиці стилів.

Q: У shadow DOM, чи впливає специфічність хоста на стилі ::part()?
A: Ні. ::part() зводиться до власного контексту специфічності компонента. ID хоста додається до рахунку опублікованої частини, але не проникає всередину тіньового DOM. Це поведінка Chrome 89+, яка регулярно дивує розробників, які припускають що глобальний каскад діє всередині shadow boundaries.

Приклади

Базовий: три селектори для одного елемента

html
<p id="intro" class="lead">Hello</p>
css
p { color: black; } /* 0,0,0,1 */ .lead { color: navy; } /* 0,0,1,0 */ #intro { color: darkgreen; } /* 0,1,0,0 - перемагає */

Абзац буде темно-зеленим. Кожен рівень у таблиці підрахунку несе в десять разів більше ваги ніж колонка нижче. Жодна кількість класів на одному елементі не переб'є один ID.

Середній рівень: перевизначення компонентної бібліотеки

css
/* Bootstrap base */ .btn { padding: 6px 12px; } /* 0,0,1,0 */ .btn.btn-primary { background: #0d6efd; } /* 0,0,2,0 */ /* Твоя тема - завантажується після Bootstrap */ .custom-theme .btn-primary { background: #e63946; } /* 0,0,2,0 - рівно, але пізніше = перемагає */

Додавання батьківського класу .custom-theme піднімає твоє правило до (0,0,2,0), збігаючись із складним селектором Bootstrap. Оскільки твій CSS завантажується після фреймворку, порядок оголошень вирішує нічию на твою користь.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?