Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «CSS псевдокласи та псевдоелементи». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Псевдокласи** вибирають елементи за станом або позицією (`:hover`, `:nth-child`); **псевдоелементи** стилізують частини елементів або вставляють віртуальний контент (`::before`, `::first-line`). Одна двокрапка - псевдоклас, подвійна - псевдоелемент. ```css button:hover { background: blue; } /* псевдоклас: стан */ .quote::before { content: '"'; } /* псевдоелемент: вставлений контент */ ``` **Ключове:** псевдоелементи створюють блоки тільки для рендерингу, без вузла в DOM; псевдокласи відповідають реальним вузлам.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**CSS псевдокласи та псевдоелементи** - це два типи селекторів з однією ключовою різницею: псевдокласи вибирають цілі елементи за станом або позицією, а псевдоелементи стилізують конкретні частини елементів або вставляють контент, якого немає в DOM. ## Теорія ### TL;DR - Псевдокласи використовують одну двокрапку (`:hover`); псевдоелементи - подвійну (`::before`) - Псевдокласи відповідають реальним вузлам DOM за станом або позицією; псевдоелементи створюють або вибирають віртуальні підчастини - Уяви псевдокласи як індикатор стану елемента (реагує на те, що відбувається); псевдоелементи як стікер, приклеєний до конкретного місця - Взаємодія з користувачем і позиція в DOM - псевдоклас. Декорація або вставка контенту - псевдоелемент. - `::before` і `::after` потребують властивості `content`, навіть якщо вона порожня ### Швидкий приклад ```css /* Псевдоклас: вибирає за позицією */ li:nth-child(odd) { background: #f0f0f0; } /* Псевдоелемент: вставляє контент перед кожним li */ li::before { content: "→ "; } ``` ```html <ul> <li>Item 1</li> <!-- сірий фон + стрілка --> <li>Item 2</li> <!-- немає фону + стрілка --> <li>Item 3</li> <!-- сірий фон + стрілка --> </ul> ``` Псевдоклас змінює вигляд залежно від позиції в DOM. Псевдоелемент додає віртуальний контент, якого немає в HTML-розмітці. ### Головна різниця Псевдокласи реагують на умови зовні елемента: дію користувача, позицію в DOM або стан (`:checked`, `:disabled`). Псевдоелементи натомість створюють анонімні блоки в дереві рендерингу браузера. Ці блоки є в движку верстки, але не мають відповідного вузла DOM. Саме тому `::before` не можна знайти через `document.querySelector` - в DOM просто нічого шукати. ### Коли що використовувати - **Стани hover і focus** на кнопках і посиланнях: `:hover`, `:focus` - **Зебра-смугування** в списках або таблицях: `:nth-child(odd)`, `:nth-child(2n)` - **Стилізація форм за станом**: `:checked`, `:disabled`, `:required` - **Декоративні іконки або лічильники** до або після елементів: `::before`, `::after` - **Буквиці або стилізований перший рядок** у статтях: `::first-letter`, `::first-line` - **Колір виділення тексту**: `::selection` Якщо додаєш порожній `<span>` тільки заради декорації - це сигнал, що `::before` або `::after` впораються без зайвої розмітки. ### Таблиця порівняння | Аспект | Псевдокласи | Псевдоелементи | |---|---|---| | **Синтаксис** | Одна двокрапка `:hover` | Подвійна двокрапка `::before` | | **Що вибирає** | Цілі існуючі елементи | Частини або віртуальні піделементи | | **Наявність у DOM** | Відповідає реальним вузлам | Генерує блоки тільки для рендерингу | | **Поширені приклади** | `:hover`, `:nth-child(2n)`, `:not()`, `:has()` | `::before`, `::after`, `::first-line`, `::selection` | | **Доступний з JS** | Так | Ні | | **Коли використовувати** | Стилізація за станом або позицією | Декоративний контент, стилізація частин елемента | ### Як браузер це обробляє Коли движок стилів розбирає селектори, псевдокласи перевіряють динамічні умови через дерево обчислених стилів. `:hover` спрацьовує при події mouseenter на елементі. Псевдоелементи, наприклад `::before`, генерують анонімні рядкові блоки в дереві верстки. Ці блоки успадковують стилі від батька, але не мають реального HTML-вузла. Змінити вміст `::before` через JS напряму не вийде - для цього зазвичай використовують CSS-змінну як міст між скриптом і псевдоелементом. ### Типові помилки **Одна двокрапка для псевдоелементів:** ```css /* Стара нотація - уникай */ p:before { content: ""; } /* Правильно */ p::before { content: ""; } ``` Одна двокрапка досі працює в більшості браузерів для зворотної сумісності, але змішує синтаксис псевдокласів і псевдоелементів. **`:nth-child` рахує не тих сусідів:** ```css .item:nth-child(2) { color: red; } ``` ```html <div><span class="item">1</span></div> <div class="item">2</div> <!-- НЕ червоний: він 1-й нащадок свого батька --> ``` `:nth-child` рахує серед сусідів одного батька, а не серед усіх `.item` у DOM. Якщо елементи знаходяться в різних контейнерах, лічильник скидається для кожного. **Відсутній `content` у `::before` або `::after`:** ```css /* Нічого не рендериться - content обов'язковий */ .icon::before { font-family: "Icons"; } /* Правильно */ .icon::before { content: ""; font-family: "Icons"; } ``` **Прибирання `:focus` outline без заміни:** ```css /* Ламає навігацію з клавіатури */ button:focus { outline: none; } ``` Прибирай outline тільки якщо замінюєш його чимось не менш помітним. WCAG 2.1 AA вимагає видимих індикаторів фокусу. Більшість багів із доступністю в продакшені, які я бачив, починаються саме з цього рядка. ### Де зустрічається в реальних проєктах - **Tailwind CSS**: варіанти `hover:`, `focus:` компілюються в псевдокласи `:hover`, `:focus` - **Bootstrap 5**: `::before`/`::after` для іконок шевронів в акордеонах і дропдаунах - **GitHub**: `:nth-child(odd)` для зебра-смугування списку PR, `::before` для декорацій лейблів - **Material-UI**: `:checked` для станів перемикачів, `::selection` для брендованого виділення тексту ### Питання на співбесіді **Q:** Яка різниця між `:hover` у CSS і `mouseenter` у JavaScript? **A:** `:hover` розповсюджується на дочірні елементи, тому при наведенні на вкладений елемент батько теж вважається hovered. `mouseenter` в JS спрацьовує тільки на прямому цільовому елементі без бульбашкового розповсюдження. **Q:** Чи можна вкладати псевдоелементи, наприклад `::before::after`? **A:** Ні. Специфікація не дозволяє псевдоелементам мати власні псевдоелементи. Замість цього використовуй реальний дочірній елемент. **Q:** Яка специфічність у `:not(.foo)`? **A:** Сам `:not()` не додає специфічності. Її визначає тільки аргумент всередині. Тому `:not(.foo)` має специфічність на рівні класу (0,1,0), як і просто `.foo`. **Q:** Як `:has()` змінює підхід до псевдокласів? **A:** `:has()` - це батьківський селектор (CSS Selectors 4), підтримується в Chrome 105+ і Safari 15.4+. `section:has(.warning) { border: 2px solid red; }` робить те, для чого раніше потрібен був JavaScript. **Q (рівень senior):** Яка різниця у продуктивності рендерингу між 100 елементами `::before` і 100 додатковими `<span>`? **A:** Псевдоелементи створюють додаткові блоки в дереві верстки, що збільшує час paint і layout. Реальні вузли DOM теж мають вартість, але браузери для них краще оптимізовані. Для великих списків з `::before` - профілюй через панель Layout у Chrome DevTools. На практиці різниця мала, але при тисячах елементів реальні вузли можуть виявитися швидшими. ## Приклади ### Зебра-смугування зі статусними іконками ```css .issue { padding: 1rem; border-bottom: 1px solid #eee; } .issue:nth-child(odd) { background: #f6f8fa; } .issue.severity-high::before { content: "🔥 "; font-size: 1.2em; } ``` ```html <div class="issue severity-high">Fix login bug</div> <!-- сірий + іконка вогню --> <div class="issue">Update docs</div> <!-- білий, без іконки --> <div class="issue severity-high">Security patch</div> <!-- сірий + іконка вогню --> ``` `:nth-child(odd)` смугує рядки за позицією. `::before` на конкретному класі додає декоративний контент тільки для пріоритетних елементів. Жодних зайвих HTML-тегів для жодного з ефектів. ### Обмежений `:nth-child` у вкладених списках ```css /* Неправильно: рахує глобально, ламається при кількох списках */ .row:nth-child(2n) { background: #f0f0f0; } /* Правильно: обмежено батьківським контейнером */ .list .row:nth-child(2n) { background: #f0f0f0; } ``` ```html <div class="list"> <div class="row">A</div> <!-- немає фону --> <div class="row">B</div> <!-- сірий --> </div> <div class="list"> <div class="row">C</div> <!-- сірий з правильною версією --> </div> ``` Без прив'язки до `.list` `:nth-child` рахує всіх сусідів `.row` по всій сторінці. Додай батьківський селектор - і кожен список отримає власний незалежний лічильник. Це часта причина помилок зі смугуванням у React-компонентах, які рендеряться в окремі контейнери.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.