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

Подписи, подсказки и ошибки формы

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

Добавим в a11y-lab самостоятельный учебный прототип проверки адреса электронной почты. Он не является существующей формой ProfessorWeb и ничего не отправляет на сервер. Это позволит рассмотреть отношения между элементами, не смешивая доступность с подключением сервиса сообщений. В примерах используйте учебный адрес, а результат проверки понимается только как оценка заполнения и синтаксиса.

Название остаётся после ввода

Вставьте следующий фрагмент после статьи, внутри main. Поле имеет видимую подпись, отдельную подсказку и место ошибки. Кнопка описывает действительный результат, а не обещает отправку:

<section aria-labelledby="feedback-title">
  <h2 id="feedback-title">Учебная проверка адреса</h2>
  <form id="feedback-form">
    <label for="feedback-email">Электронная почта — обязательно</label>
    <p id="feedback-email-hint">
      Например, reader@example.com. Проверяется формат, письмо не отправляется.
      Учебная проверка включается после загрузки JavaScript.
    </p>
    <input id="feedback-email" name="email" type="email" required disabled
           autocomplete="email" aria-describedby="feedback-email-hint">
    <p id="feedback-email-error" hidden></p>
    <button type="submit" disabled>Проверить адрес</button>
    <p id="feedback-status"></p>
  </form>
</section>

label связан с существующим id через for. Нажатие на подпись и программное представление поля получают одну связь. Текст остаётся на странице после заполнения. placeholder не заменяет эту роль: он может исчезнуть при вводе и предназначен для другой подсказки. Здесь пример адреса вынесен в отдельный абзац, который остаётся доступным.

required задаёт обязательность средствами HTML, а слова «обязательно» сообщают её видимо. Звёздочка без объяснения потребовала бы знания условного обозначения. Если в форме несколько обязательных полей, общая инструкция может дополнить их названия, но не должна заставлять угадывать, к каким полям относится правило. WAI рассматривает способы подписывания полей.

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

Одна проверка и конкретная причина

Добавьте следующий фрагмент в конец lesson.js. Обработчики навигации и фокуса остаются. После установки обработчика включается собственная клиентская проверка; исходные ограничения HTML сохраняются как описание ожидаемых данных:

const feedbackForm = document.getElementById('feedback-form');
const feedbackEmail = document.getElementById('feedback-email');
const feedbackError = document.getElementById('feedback-email-error');
const feedbackStatus = document.getElementById('feedback-status');

function showEmailError(message) {
  feedbackError.textContent = message;
  feedbackError.hidden = false;
  feedbackEmail.setAttribute('aria-invalid', 'true');
  feedbackEmail.setAttribute('aria-describedby',
    'feedback-email-hint feedback-email-error');
}

function clearEmailError() {
  feedbackError.textContent = '';
  feedbackError.hidden = true;
  feedbackEmail.removeAttribute('aria-invalid');
  feedbackEmail.setAttribute('aria-describedby', 'feedback-email-hint');
}

feedbackForm.addEventListener('submit', event => {
  event.preventDefault();
  feedbackStatus.textContent = '';
  let message = '';
  if (feedbackEmail.validity.valueMissing) {
    message = 'Введите электронную почту, например reader@example.com.';
  } else if (feedbackEmail.validity.typeMismatch) {
    message = 'Проверьте формат адреса: ожидается имя@домен.';
  }
  if (message) {
    showEmailError(message);
    feedbackEmail.focus();
    return;
  }
  clearEmailError();
  feedbackStatus.textContent = 'Адрес прошёл учебную проверку. Ничего не отправлено.';
});

feedbackForm.noValidate = true;
feedbackEmail.disabled = false;
feedbackForm.querySelector('button[type="submit"]').disabled = false;

preventDefault здесь необходим: успешная учебная проверка не должна превращаться в обычную отправку формы. Исходные поле и кнопка отключены; они включаются только после установки обработчика. При недоступном JavaScript учебный ввод остаётся неактивным. Если просто убрать disabled, форма без action и method с именованным полем могла бы выполнить обычный GET текущего адреса и передать значение в строке запроса. Отсутствие собственного сервера само по себе этого не предотвращает. Здесь выбран явный исходный режим; для настоящей отправки потребуются отдельные обработчик, результат сервера и подходящая поддержка работы без улучшения.

Флаг noValidate отключает стандартную блокировку отправки, чтобы собственный обработчик мог показать выбранные сообщения. Он не удаляет свойства validity: ограничения поля продолжают определять их значения. Если оставить стандартное блокирование и ожидать, что submit всегда сработает при пустом поле, пользовательский текст может не появиться. Здесь мы выбираем одну понятную модель, а не две конкурирующие.

Для пустого значения сообщение объясняет, что ввести. Для неверного формата оно называет проверяемое условие. «Ошибка 400» не помогла бы исправить пользовательские данные. При этом проверка синтаксиса не подтверждает существование почтового ящика или доставку письма. Название кнопки и сообщение успеха сохраняют именно эту границу результата.

Связь ошибки с полем

После неудачной попытки текст ошибки становится видимым, поле получает aria-invalid, а описание включает идентификатор сообщения. Фокус переносится к полю, потому что исправление связано с одним конкретным местом. Для длинной формы с несколькими ошибками может быть удобна отдельная сводка со ссылками на поля, но она потребует другого маршрута. Не нужно имитировать её для одного поля.

При успешной повторной проверке ошибка очищается и связь возвращается к исходной подсказке. Если удалить только красный фон, оставив aria-invalid="true", визуальная и программная информация разойдутся. Если скрыть текст, но оставить старое сообщение в описании, его состояние тоже потребует оценки. Функция очистки нужна как полноценная обратная операция к показу ошибки.

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

Текст сообщения задаётся через textContent. Это позволяет использовать обычную строку без превращения её в разметку. Для стандартного объяснения ошибки дополнительные HTML-элементы не нужны. Если будущий обработчик включает фрагмент пользовательского значения, вставка как текст также сохраняет его буквальный вид. Смысловая связь создаётся идентификаторами и атрибутами, а не разбором строки сообщения как HTML.

В этой версии результат обновляется при повторной активации кнопки. Мы не объявляем ошибку заново на каждый введённый символ. Это намеренное ограничение простого прототипа: слишком частые изменения способны мешать чтению и редактированию. Если требуется проверка во время ввода, её моменты и сообщения нужно проектировать отдельно, учитывая незавершённое значение.

Добавьте в CSS небольшое оформление. Смысл ошибки уже передаётся текстом; цвет поддерживает его, а не заменяет:

#feedback-form input {
  display: block;
  inline-size: min(100%, 30rem);
  padding: 0.75rem;
  font: inherit;
}

#feedback-form input[aria-invalid="true"] {
  border: 2px solid #852333;
}

#feedback-email-error { color: #852333; }
#feedback-email-error[hidden] { display: none; }

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

Проверка результата и следующая задача

Для будущего наблюдения рассмотрите пустое значение, неверный формат и учебный адрес reader@example.com. В каждом случае должно быть понятно, что проверялось и какое действие следует дальше. WAI, Validating Input объясняет роль ошибок и обратной связи; наш стенд показывает только один небольшой клиентский вариант.

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