Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Специфічність селекторів CSS». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Специфічність CSS-селекторів** - це рахунок (A,B,C,D), який визначає яке правило застосовується коли кілька селекторів збігаються на одному елементі. ```css div { color: blue; } /* 0,0,0,1 */ .box { color: red; } /* 0,0,1,0 - перемагає */ #main { color: green; } /* 0,1,0,0 - перемагає обидва */ ``` **Ключове:** порівнюй колонки зліва направо. Вища колонка виграє. При рівних рахунках перемагає останнє оголошення.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Специфічність 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"` | | ID | B (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 каскаді](/questions/css-cascade). - Ти будуєш систему тематизації. Ланцюжок класів `.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 завантажується після фреймворку, порядок оголошень вирішує нічию на твою користь.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.