</> Praktum

Главная / CSS / Псевдоклассы CSS

Урок · CSS

Псевдоклассы: стили для состояний

Редакция PraktumОбновлено 16 минут на урок с практикойУровень: новичок

Псевдокласс — часть селектора после одного двоеточия, которая выбирает элемент в определённом состоянии или положении: на него навели мышь (:hover), он в фокусе (:focus-visible), поле отмечено (:checked), это первый ребёнок (:first-child). Псевдоклассы избавляют от лишних классов в HTML.

Коротко
  • Одно двоеточие — псевдокласс (состояние), два — псевдоэлемент (часть элемента).
  • :focus-visible показывает рамку фокуса с клавиатуры и не рисует её после клика мышью.
  • Порядок для ссылок: обычная, :visited, :hover, :focus-visible, :active.
  • :nth-child(2n) — каждый второй, :not(:last-child) — все, кроме последнего.
  • :has() выбирает родителя по содержимому: .card:has(img).

Зачем этот урок сейчас

Интерфейс должен отвечать на действия: кнопка темнеет при наведении, поле краснеет при ошибке, у ссылки видна рамка, когда в неё перешли с клавиатуры. Без псевдоклассов пришлось бы переключать классы JavaScript-ом. С ними CSS делает это сам.

Минимальный пример

.button { background: #2a62d9; color: #fff; }
.button:hover { background: #244dc8; }
.button:active { transform: translateY(1px); }
.button:focus-visible { outline: 3px solid #ffbc42; outline-offset: 3px; }
.button:disabled { opacity: .55; cursor: not-allowed; }

Одна кнопка — пять состояний без единой строки JavaScript. :hover срабатывает при наведении, :active — пока кнопку держат нажатой, :focus-visible — при переходе клавишей Tab, :disabled — если у кнопки есть атрибут disabled.

Основные псевдоклассы

Псевдоклассы по группам
ГруппаПсевдоклассыКогда срабатывает
Действия:hover, :active, :focus, :focus-visible, :focus-withinнаведение, нажатие, фокус, фокус внутри
Ссылки:link, :visitedссылка не посещена / посещена
Формы:checked, :disabled, :required, :invalid, :valid, :placeholder-shown, :user-invalidсостояние поля
Структура:first-child, :last-child, :nth-child(), :nth-of-type(), :only-child, :emptyположение среди соседей
Логика:not(), :is(), :where(), :has()условия внутри селектора
Страница:root, :targetкорень документа, элемент из #якоря в адресе

Фокус клавиатуры: focus или focus-visible

:focus срабатывает при любом фокусе, в том числе после клика мышью — поэтому дизайнеры и убирают рамку. :focus-visible браузер включает, когда рамка действительно нужна: при навигации с клавиатуры и в текстовых полях.

a:focus-visible,
button:focus-visible {
  outline: 3px solid #ffbc42;
  outline-offset: 3px;
}

Не пиши outline: none без равноценной замены: человек с клавиатурой перестанет видеть, где он находится. :focus-within срабатывает на родителе, если фокус у любого потомка, — удобно подсветить всю форму поиска, когда курсор в поле.

У состояний ссылки одинаковая специфичность, поэтому побеждает правило, написанное позже. Если поставить :hover после :active, нажатая ссылка будет выглядеть как наведённая. Привычный порядок:

a { color: #2a62d9; }
a:visited { color: #6b4fbb; }
a:hover { text-decoration-thickness: 2px; }
a:focus-visible { outline: 2px solid currentColor; }
a:active { color: #1b3f99; }

Для :visited браузеры разрешают менять только цвета: это защита приватности, чтобы сайт не мог вычислить, где ты был.

Состояния формы

input:checked + label { font-weight: 700; }
input:required + label::after { content: " *"; color: #c93636; }
input:user-invalid { border-color: #c93636; }
button:disabled { opacity: .55; cursor: not-allowed; }

:invalid срабатывает сразу: пустое обязательное поле невалидно ещё до того, как человек что-то ввёл, и форма загорается красным при загрузке. :user-invalid ждёт, пока пользователь взаимодействовал с полем, и подходит для подсветки ошибок лучше. В старых браузерах его заменяет связка :invalid:not(:placeholder-shown).

Формула :nth-child

В скобках :nth-child() пишут число, ключевое слово или формулу an+b, где n перебирает 0, 1, 2…

Примеры nth-child
ЗаписьКого выберет
:nth-child(3)третий элемент
:nth-child(2n) или even2, 4, 6…
:nth-child(2n+1) или odd1, 3, 5…
:nth-child(3n)каждый третий
:nth-child(-n+3)первые три
:nth-child(n+4)с четвёртого и дальше

:nth-child считает всех соседей подряд, а :nth-of-type — только элементы того же тега. Если в контейнере перемешаны h2 и p, p:nth-child(2) может ничего не выбрать, а p:nth-of-type(2) выберет второй абзац.

:not, :is, :where и :has

.menu li:not(:last-child) { border-bottom: 1px solid #e5e7eb; }
:is(h1, h2, h3):hover { color: #2a62d9; }
:where(.article) a { color: inherit; }
.card:has(img) { padding-top: 0; }
label:has(input:checked) { background: #eef2ff; }

:is() сокращает длинные списки селекторов. :where() делает то же, но с нулевой специфичностью — удобно для базовых стилей, которые легко перебить. :has() впервые позволил выбирать родителя по содержимому: карточка с картинкой, подпись с отмеченной галочкой, форма с невалидным полем.

Специфичность псевдоклассов

Псевдокласс весит как класс: a:hover перебивает a, но проигрывает .nav a. Исключения: :where() всегда весит ноль, а :is(), :not() и :has() берут вес самого тяжёлого селектора в скобках. :is(#menu, a) весит как id — это легко забыть и потом удивляться, почему стиль не перебивается.

Псевдокласс или класс

Псевдокласс

Состояние, которое браузер знает сам: наведение, фокус, отмечено, первый в списке.

Класс

Состояние, которое знает только твоё приложение: меню открыто, вкладка выбрана. Его переключают через JavaScript и classList.

Частые ошибки

  • Прячут важное действие только под :hover — на телефоне наведения нет.
  • Пишут outline: none и не дают замены для фокуса.
  • Красят :invalid сразу, и пустая форма горит красным до первого ввода.
  • Ставят :hover после :active, и нажатие не видно.
  • Пишут сложный :nth-child(4n+2) там, где понятнее отдельный класс.

Доступность

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

Поддержка браузерами

Базовые псевдоклассы работают везде. :focus-visible, :is() и :where() поддерживаются всеми актуальными браузерами. :has() и :user-invalid появились позже и доступны в свежих версиях Chrome, Safari и Firefox — для старых браузеров оставляй стили, которые не ломают страницу без них.

Практика

Сделай список из пяти пунктов с разделителем между пунктами, но не после последнего. Подкрась каждый второй пункт. Добавь кнопку с состояниями hover, active, focus-visible и disabled. Затем пройди страницу клавишей Tab и убедись, что фокус видно везде.

Показать решение
.list li:not(:last-child) { border-bottom: 1px solid #e5e7eb; }
.list li:nth-child(even) { background: #f8fafc; }
.button:hover { background: #244dc8; }
.button:active { transform: translateY(1px); }
.button:focus-visible { outline: 3px solid #ffbc42; outline-offset: 3px; }
.button:disabled { opacity: .55; cursor: not-allowed; }

Открыть песочницу

Частые вопросы

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

Псевдокласс (одно двоеточие) выбирает элемент в состоянии: :hover, :checked. Псевдоэлемент (два двоеточия) выбирает часть элемента или создаёт новую: ::before, ::first-line.

Чем :focus отличается от :focus-visible?

:focus срабатывает при любом фокусе, включая клик мышью. :focus-visible — только когда рамка нужна, в первую очередь при навигации с клавиатуры.

Почему :hover не работает на телефоне?

У сенсорного экрана нет наведения. Браузер может эмулировать hover после касания, но полагаться на это нельзя. Важные действия делай видимыми сразу.

Как выбрать каждый второй элемент?

:nth-child(2n) или :nth-child(even) — чётные, :nth-child(odd) — нечётные.

Можно ли выбрать родителя в CSS?

Да, через :has(): .card:has(img) выберет карточки, внутри которых есть картинка. Псевдокласс поддерживается в актуальных версиях браузеров.

Источники

Практикумы по теме