Псевдоклассы и псевдоэлементы в CSS: ошибки новичков
Почему ::before не появляется, hover не работает на мобильном, а :nth-child считает не то, что вы думаете. Разбор частых промахов с псевдоклассами и псевдоэлементами и как их починить.

Вы написали .card::before { content: 'Новинка'; }, а на странице пусто. Или добавили :hover, и на телефоне ничего не подсвечивается. Или :nth-child(2) выделяет не тот блок. Всё это — типичные грабли псевдоклассов и псевдоэлементов. Ниже разберём восемь ошибок, которые чаще всего ломают вёрстку у тех, кто только освоил базовый CSS. Актуально для всех современных браузеров (2024 год) и любой версии CSS, которую вы пишете руками.
Перед началом стоит держать в голове разницу. Псевдокласс описывает состояние элемента, который уже есть в разметке: наведён курсор, поле в фокусе, ссылка посещена. Псевдоэлемент создаёт часть, которой в HTML нет: значок перед текстом, первая буква, полоска после блока.
Двоеточие: одно или два
Первая ошибка вылезает ещё до логики. Псевдоклассы пишутся с одним двоеточием, псевдоэлементы — с двумя.
:hover,:focus,:nth-child()— псевдоклассы, одно двоеточие;::before,::after,::first-line— псевдоэлементы, два двоеточия.
Двойное двоеточие для псевдоэлементов ввели в CSS3, чтобы их визуально отличать от псевдоклассов. Старый синтаксис с одним двоеточием (:before) браузеры пока понимают ради совместимости, но новый код пишите правильно — ::before. А вот псевдокласс с двумя двоеточиями (::hover) не сработает нигде.
::before без content не появляется
Самая частая причина «псевдоэлемент не виден» — забытое свойство content. Без него ::before и ::after вообще не создаются, даже если вы задали им размеры, фон и границу.
/* Не сработает — нет content */
.badge::before {
width: 8px;
height: 8px;
background: red;
}
/* Сработает */
.badge::before {
content: ""; /* пустая строка, но content обязателен */
display: inline-block;
width: 8px;
height: 8px;
background: red;
}
Даже если вам нужен пустой декоративный кружок, а не текст, ставьте content: "";. Это не опечатка, а обязательное условие существования псевдоэлемента.
Псевдоэлемент внутри одиночного тега
Вторая засада с ::before и ::after: они вставляются внутрь элемента, как первый и последний потомок его содержимого. У тегов, которые не имеют содержимого — <img>, <input>, <br>, <hr> — вставлять некуда, поэтому псевдоэлементы на них не работают.
Если нужно добавить значок рядом с картинкой или полем ввода, оберните их в контейнер и вешайте псевдоэлемент на обёртку:
<span class="field">
<input type="text">
</span>
.field::after {
content: "*";
color: red;
}
:nth-child против :nth-of-type
Тут ломается интуиция. :nth-child(n) считает все дочерние элементы родителя подряд, а не только те, что совпадают по тегу с вашим селектором. Классический промах:
<div class="box">
<h2>Заголовок</h2>
<p>Первый</p>
<p>Второй</p>
</div>
/* Ожидаете «Первый», получите ничего */
.box p:nth-child(1) { color: red; }
Селектор читается так: «элемент p, который является первым ребёнком родителя». Но первый ребёнок — это <h2>, а не <p>. Условие не выполняется, стиль не применяется. Чтобы посчитать именно абзацы, нужен :nth-of-type:
/* Покрасит первый абзац */
.box p:nth-of-type(1) { color: red; }
Запомните формулировку::nth-child— «второй ребёнок вообще, и это должен быть p».:nth-of-type— «второй p среди детей». Пока проговариваете вслух — не путаетесь.
:hover на мобильных
На сенсорном экране нет курсора, поэтому :hover ведёт себя непредсказуемо. На части устройств стиль срабатывает после тапа и «залипает» до следующего касания в стороне. Если вы прячете важный контент за :hover (например, выпадающее меню), на телефоне его может быть не открыть или не закрыть.
Не делайте единственный доступ к функциональности через наведение. Для чисто декоративных эффектов, которые не нужны на тач-устройствах, отделяйте их медиавыражением:
@media (hover: hover) {
.card:hover {
transform: translateY(-4px);
}
}
hover: hover срабатывает только там, где есть настоящий указатель. На тач-устройствах блок игнорируется, и «залипания» не будет.
Порядок псевдоклассов у ссылок
У ссылок четыре состояния, и порядок их объявления в CSS влияет на результат из-за одинаковой специфичности. Если :hover написать раньше :visited, посещённая ссылка перебьёт наведение, и подсветки при наведении на такую ссылку вы не увидите.
Рабочий порядок — LVHA: link, visited, hover, active.
a:link { color: blue; }
a:visited { color: purple; }
a:hover { color: red; }
a:active { color: orange; }
:focus, который убрали ради красоты
Частый «фикс» новичка: *:focus { outline: none; } — рамка вокруг кнопок и полей мешает дизайну, её убирают. Но именно эта рамка показывает пользователю клавиатуры, где он сейчас находится. Убрав её без замены, вы ломаете навигацию с клавиатуры.
Если рамка мешает при клике мышью, используйте :focus-visible — он показывает обводку только при навигации с клавиатуры, а при клике мышью её не будет:
button:focus:not(:focus-visible) {
outline: none;
}
button:focus-visible {
outline: 2px solid #005fcc;
}
:focus-visible поддерживается всеми современными браузерами. Полностью убирать фокус без альтернативы — не оптимизация, а поломка доступности.
Специфичность псевдоклассов
Псевдокласс добавляет к специфичности столько же, сколько класс. Псевдоэлемент — столько же, сколько тег. Из-за этого a:hover может проиграть более «тяжёлому» селектору, и стиль не применится, хотя синтаксически всё верно.
| Селектор | Что это | Вклад в специфичность |
|---|---|---|
:hover | псевдокласс | как класс |
:nth-child() | псевдокласс | как класс |
::before | псевдоэлемент | как тег |
::first-line | псевдоэлемент | как тег |
Если ваш :hover не срабатывает, а синтаксис верный, откройте DevTools, найдите элемент и посмотрите во вкладке стилей, не перечёркнут ли ваш селектор более специфичным.
Если не получилось: короткий чек-лист
- Псевдоэлемент не виден — проверьте, есть ли
content, и не висит ли он на<img>или<input>. - :nth-child выделяет не то — вероятно, среди детей есть элементы других тегов; попробуйте
:nth-of-type. - :hover не срабатывает или «залипает» — вы на тач-устройстве; вынесите эффект в
@media (hover: hover). - Стиль перечёркнут в DevTools — проиграл по специфичности; уточните селектор или уменьшите вес конкурента.
- Эффект наведения на посещённых ссылках пропал — нарушен порядок LVHA.
Что делать дальше
Откройте DevTools в Chrome или Firefox: в панели стилей можно принудительно включить состояние :hover, :focus, :active у выбранного элемента и не гонять курсором по странице. Это ускоряет отладку в разы. Дальше стоит разобрать функциональные псевдоклассы :is(), :where() и :has() — они меняют подход к группировке селекторов и специфичности, но это отдельная тема. Проверяйте вёрстку не только мышью, но и клавишей Tab — так вы сразу увидите, не сломали ли фокус.
Разбор научной статьи: Сложный текст — понятным языком, с терминами
Спросить за 12 ₽Источники: MDN Web Docs: Псевдоклассы, MDN Web Docs: Псевдоэлементы
Частые вопросы
Почему мой ::before не появляется, хотя стили заданы?
В чём разница между :nth-child и :nth-of-type?
Одно двоеточие или два — как не путаться?
А если у меня старый браузер — :focus-visible и hover: hover сработают?
Можно ли просто убрать outline у кнопок, если он портит дизайн?
Почему мой :hover перечёркнут в DevTools и не работает?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.