Чем отличаются псевдоклассы от псевдоэлементов в CSS?

JuniorCSS #css #pseudo

Вопрос

В чём разница между псевдоклассами (:hover, :nth-child) и псевдоэлементами (::before, ::after)?

Короткий ответ

Псевдокласс описывает особое состояние или позицию уже существующего элемента (наведение курсора, первый ребёнок, невалидное поле). Псевдоэлемент описывает часть элемента или генерирует новый узел, которого нет в разметке (первая строка текста, содержимое до/после элемента). По синтаксису псевдоклассы пишутся с одним двоеточием, псевдоэлементы — с двумя.

Подробный ответ

Псевдоклассы (:single-colon) выбирают элементы, которые уже есть в DOM, но находятся в определённом состоянии или позиции относительно других элементов:

  • состояния взаимодействия: :hover, :focus, :active, :visited;
  • состояния формы: :checked, :disabled, :required, :valid/:invalid;
  • структурные/позиционные: :first-child, :last-child, :nth-child(n), :only-child;
  • логические: :not(), :is(), :where(), :has().

Псевдоэлементы (::double-colon) обращаются к части содержимого элемента, которая не имеет собственного узла в DOM, либо создают новый генерируемый узел:

  • ::before/::after — вставляют сгенерированный контент до/после содержимого элемента; обязательно требуют свойство content (даже пустое content: ""), иначе не отрендерятся. Используются для декоративных элементов, иконок, кавычек, счётчиков — без засорения HTML лишними тегами.
  • ::first-line/::first-letter — стилизуют часть текстового содержимого (первую строку, первую букву), которую невозможно выделить обычным селектором.
  • ::placeholder — стилизует плейсхолдер input.
  • ::selection — стилизует выделенный пользователем текст.

Синтаксис: по спецификации CSS3 псевдоэлементы должны писаться с двумя двоеточиями (::before), чтобы отличать их от псевдоклассов, но по историческим причинам браузеры до сих пор поддерживают одинарное двоеточие для старых псевдоэлементов (:before, :after) — в новом коде стоит явно использовать двойное.

Ключевое смысловое отличие: псевдокласс никогда не создаёт новый узел в дереве рендеринга, он лишь применяет стили к существующему элементу при определённом условии; псевдоэлемент же может фактически добавить визуальный контент, которого не было в разметке.

Пример

/* псевдокласс: состояние существующего элемента */
.button:hover {
  background-color: #2563eb;
}

.list li:nth-child(odd) {
  background-color: #f5f5f5;
}

input:invalid {
  border-color: crimson;
}

/* псевдоэлемент: генерирует контент, которого нет в HTML */
.tooltip::before {
  content: "";
  position: absolute;
  top: -4px;
  border: 4px solid transparent;
  border-bottom-color: #333;
}

.required-field::after {
  content: " *";
  color: crimson;
}

.article p::first-letter {
  font-size: 2em;
  font-weight: bold;
}

::selection {
  background-color: #ffe58f;
}

Дополнительные вопросы

  • Почему ::before/::after не рендерятся без свойства content?
  • Чем :is() и :where() отличаются друг от друга по влиянию на specificity?
  • Можно ли добавить ::before к элементу <img> и почему это не сработает?