Перейти к содержанию

Состояния ссылок, полей и кнопок

Оформим состояния ссылок и элементов управления, сохранив встроенное поведение формы и раскрывающегося блока.

Каталог имеет сетку, адаптивную шапку и общие переменные. Но интерфейс существует не только в неподвижном состоянии. Ссылку можно выбрать указателем или клавиатурой, поле получает фокус, кнопка нажимается, а дополнительное объяснение раскрывается. В этом уроке будем использовать состояния самих HTML-элементов, не добавляя JavaScript и не вводя отдельный класс для каждого движения пользователя.

Сначала важно отделить действие и его представление. Отправка формы уже описана form и button, переход курса — ссылкой. CSS лишь показывает состояние. Если удалить оформление, механизм должен остаться. Такой подход позволяет изучать псевдоклассы без иллюзии, что изменение цвета кнопки выполняет отправку данных.

Фокус и наведение

Добавьте в конец файла два правила:

.layout-lab a:hover {
  text-decoration-thickness: .15em;
}
.layout-lab :is(a, button, input, select, summary):focus-visible {
  outline: 3px solid #83eba0;
  outline-offset: 3px;
}

При наведении ожидается более заметное подчёркивание ссылки. Во время клавиатурного перехода выбранный элемент должен получать рамку фокуса. :focus-visible учитывает решение браузера о том, нужно ли показать видимый индикатор в текущем способе взаимодействия. Нельзя свести его к правилу «только Tab и никогда мышь»: у некоторых элементов и действий поведение будет отличаться. Назначение псевдокласса описано в MDN.

Контур не должен вытеснять соседние коробки так же, как добавленная рамка. outline-offset отодвигает его от края элемента. Это помогает сохранить геометрию карточки при переходах. Не убирайте стандартный индикатор без собственного понятного варианта. Выбор элемента клавиатурой должен быть наблюдаемым, иначе ссылки формально работают, но пользователь не понимает текущего положения.

Наведение нельзя делать единственным способом получить важную информацию. На сенсорном экране оно не обязано повторять поведение мыши. У нас название и описание всегда видны, а наведение лишь уточняет представление ссылки. Это небольшое изменение, соответствующее исходному назначению элемента. Подробный маршрут фокуса рассмотрен в курсе доступности.

Элементы управления наследуют шрифт

Теперь оформим учебную заявку. Эти правила не меняют её параметры и не добавляют обработчик:

.layout-lab .lab-request label { display: block; }
.layout-lab .lab-request form > * + * { margin-block-start: 1rem; }
.layout-lab .lab-request :is(input, select, button) {
  font: inherit;
  border: 1px solid var(--lab-line);
  border-radius: .35rem;
  padding: .6rem .75rem;
  max-inline-size: 100%;
}
.layout-lab .lab-request :is(input, select) {
  background: var(--lab-surface);
  color: var(--lab-ink);
}
.layout-lab .lab-request button {
  background: var(--lab-accent);
  color: #fff;
  cursor: pointer;
}
.layout-lab .lab-request button:active {
  transform: translateY(1px);
}

Ожидается подпись над каждым полем, шрифт в согласованном стиле и фиолетовая кнопка. Максимальная ширина предотвращает требование места больше контейнера, но не задаёт одинаковую ширину всем элементам. Если длинный вариант списка делает управление неудобным, его нужно отдельно проверить на узком окне. Цвета интерфейса используют роли из предыдущего урока.

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

Кнопка имеет type="submit", а поле — required. Оформление не отменяет встроенную проверку. Нажатие с пустым именем должно остаться заблокированным браузером, заполненная форма должна открыть receipt.html. Мы не задаём глобальное appearance: none ради одинакового внешнего вида всех контролов. Подобное решение потребовало бы дополнительных деталей, например собственного отображения выбора в select.

Раскрывающееся объяснение без JavaScript

После раздела заявки, но внутри основной оболочки, добавьте блок:

<details class="lab-details">
  <summary>Как проходить курс</summary>
  <p>Откройте оглавление, начните с первого урока и переходите
     к следующим материалам по порядку.</p>
</details>

Для него достаточно небольшого дополнения:

.layout-lab .lab-details { margin-block-start: 1.5rem; }
.layout-lab .lab-details summary { cursor: pointer; }
.layout-lab .lab-details[open] summary { margin-block-end: .5rem; }

Ожидается краткая подпись, по которой можно раскрыть объяснение. Атрибут open отражает раскрытое состояние; его устройство описано в MDN. Селектор проверяет его наличие и добавляет расстояние перед текстом. Не нужен свой класс opened, который пришлось бы синхронизировать скриптом. Встроенный элемент уже хранит нужное состояние.

Это дополнительная подсказка, а не единственное место с критически важными условиями. Текст о том, что заявка не сохраняется, остаётся видимым перед формой. Раскрытие не должно быть обязательным для понимания назначения кнопки. В противном случае читатель может выполнить действие на основании неполной информации. Выбор элемента также не означает, что любое сложное меню можно заменить details без анализа задач.

Проверка состояний по отдельности

Откройте страницу и двигайтесь по элементам клавишей Tab. Ожидаются видимые контуры у ссылок, поля, списка, кнопки и summary. Затем сравните наведение мышью с клавиатурным фокусом. Это разные состояния, которые иногда присутствуют вместе. Порядок CSS-правил и специфичность по-прежнему определяют результат, если оба объявления меняют одно свойство.

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

Состояние фокуса принадлежит конкретному элементу. Если хочется выделять всю группу формы, можно отдельно использовать :focus-within на её обёртке: условие совпадает, пока фокус находится внутри; это поведение описано в MDN. Это не заменяет индикатор выбранного поля. Общий фон сообщает область работы, а контур показывает точное положение. В нашем основном макете достаточно второго, поэтому дополнительное правило оставим возможным опытом.

Для проверки отправки сравните обычное нажатие мышью и активацию кнопки клавиатурой. Ожидается один контракт параметров, несмотря на разные способы выбора. Если добавить скрипт, который перехватывает только щелчок, можно случайно получить разные маршруты. Здесь нативная форма сохраняет общую модель без такого обработчика.

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

Скрытый элемент, отключённый элемент и элемент без собственного визуального выделения — разные состояния. Например, отключённая кнопка не должна изображать готовое действие лишь более бледным цветом; атрибут меняет возможность взаимодействия. В нашем макете кнопка всегда доступна, а допустимость заполнения проверяется формой. Если позднее появится длительная отправка, потребуется определить момент блокировки и способ сообщить результат. Одного нового CSS-класса для такой модели недостаточно.

Также не помещайте кнопку внутрь summary ради отдельного значка раскрытия. Уже существующий элемент является управляющей подписью. Вложенное независимое действие усложнит выбор и объяснение результата. Для нашего короткого текста достаточно одного нативного раскрытия с ясным именем, а декоративный значок при необходимости должен оставаться частью представления, не добавляя новый маршрут.

Для самостоятельного опыта уберите только правило :active. Отправка должна остаться прежней, исчезнет лишь движение кнопки. Затем уберите контур и оцените, насколько сложнее следить за маршрутом; после опыта обязательно восстановите его. В следующем уроке состояние компонента будет зависеть уже не от взаимодействия, а от ширины его собственного контейнера.

Оглавление курса