Урок · CSS
Псевдоклассы: стили для состояний
Псевдокласс — часть селектора после одного двоеточия, которая выбирает элемент в определённом состоянии или положении: на него навели мышь (: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(3) | третий элемент |
:nth-child(2n) или even | 2, 4, 6… |
:nth-child(2n+1) или odd | 1, 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) выберет карточки, внутри которых есть картинка. Псевдокласс поддерживается в актуальных версиях браузеров.
Источники
Практикумы по теме
- Поле с ошибкой видно до отправки — Браузер умеет пометить пустое обязательное поле. Цвета без слов недостаточно.
- Рамка фокуса для клавиатуры — Человек на Tab должен видеть, где он находится. Рамка у каждого клика мышкой для этого не нужна.
- Четыре состояния ссылки — Обычная, посещённая, наведённая и нажатая ссылка — это четыре разных момента.