Контактная форма по EAA: пошаговое руководство для веб-разработчиков
Информация о статье

Автор: Дмитрий Дугарев
Европейский акт о доступности (EAA) [1] вступил в силу и ставит цифровую доступность в центр внимания. Для Вас как разработчика это означает: каждый элемент, особенно контактная форма, должен быть доступен для использования абсолютно каждому. Недоступная форма — это не только юридический риск, но и упущенная возможность связаться с потенциальными клиентами [2].
В этом руководстве мы шаг за шагом создадим форму, которая соответствует актуальным законодательным требованиям. Я объясню Вам не только, что нужно делать, но и почему, с соответствующими ссылками на официальные руководства. В итоге у Вас будет надежная, доступная контактная форма, которую Вы сможете использовать в своих проектах.
Вот живая демонстрация готовой, доступной контактной формы:
Основная структура и инструкции
Основу любой доступной формы составляет чистый, семантический HTML. Это начинается с правильной связи меток (<label>) с их полями ввода (<input>).
Каждое поле требует постоянно видимой <label> [3], которая связана с id поля через атрибут for [4]. Заполнители, которые исчезают при вводе, не являются заменой метки [5]. На Рисунке 1.1 Вы видите примерную структуру доступной формы, которую мы будем создавать далее.
Кроме того, Вы должны размещать общие инструкции, такие как обозначение обязательных полей, перед формой, чтобы пользователи знали, что их ожидает [6]. Метки полей ввода также должны располагаться перед полем [7].
Открыть текстовое описание для "Схематическая базовая структура доступной формы"
Эта диаграмма показывает основные строительные блоки формы.
- Контактная форма: Самый внешний элемент, который все включает. Он содержит инструкции, обзор ошибок, группы полей (Fieldsets) и кнопку «Отправить».
- Группы полей (
<fieldset>): Служат для группировки связанных полей ввода. Они содержат подпись (<legend>) и сами поля ввода. - Поля ввода: Каждое поле состоит из подписи (
<label>), связанной с полем ввода (<input>). Дополнительно может быть помощь при вводе, предоставляющая дополнительную информацию о поле.
Вот пример кода для основной структуры:
<p id="form-instructions-demo">
Все поля, отмеченные звездочкой (*), являются обязательными.
</p>
<div
id="error-summary-demo"
class="contact-form__error-summary"
role="alert"
tabIndex="-1"
hidden
aria-labelledby="error-summary-title-demo"
>
<h2 id="error-summary-title-demo">Ваши данные содержат ошибки</h2>
<p id="error-summary-intro"></p>
<ul class="contact-form__error-summary-list">
</ul>
</div>
<form id="contact-form-demo" class="contact-form__form" novalidate>
<div class="contact-form__group">
<label for="fname-demo" class="contact-form__label">
<span class="contact-form__error-prefix" hidden>Ошибка: </span>
Имя*
</label>
<input
type="text"
id="fname-demo"
name="fname"
required
aria-required="true"
aria-invalid="false"
aria-describedby="fname-error-demo"
class="contact-form__input"
/>
<div
id="fname-error-demo"
class="contact-form__error-message"
hidden
></div>
</div>
<button type="submit" class="contact-form__submit-button">
Отправить сообщение
</button>
</form>
Мы используем novalidate в теге <form>, чтобы отключить недоступную стандартную валидацию браузера и управлять обработкой ошибок самостоятельно.
Порядок фокуса и видимость для управления с клавиатуры
Форма, которой нельзя управлять без мыши, не является доступной. Здесь важны два критерия:
-
Логический порядок фокуса: Пользователи, навигирующие с помощью клавиши
Tab, должны иметь возможность перемещаться по форме в логической и интуитивно понятной последовательности [9]. При чистой структуре HTML (сверху вниз) это обычно обеспечивается автоматически. Более сложные макеты (например, с CSS Grid или Flexbox) должны быть проверены на это. -
Видимый фокус: Каждый интерактивный элемент (Input, Button, Link) должен показывать четко видимый индикатор, когда он получает фокус [10]. Это индикатор «Вы находитесь здесь» для пользователей клавиатуры. Никогда не скрывайте стиль
outlineбез предоставления лучшей, четко видимой замены.
Логическая группировка полей ввода
Связанная информация (например, «Имя» и «Фамилия» или группа радиокнопок) также должна быть программно распознаваема как группа [11]. Для этого Вы используете <fieldset> и <legend> [12] или, в качестве альтернативы, ARIA-роли, такие как role="group" [13].
<fieldset> окружает группу, а <legend> дает ей имя, которое считывается скринридером [12]. Без этой структуры пользователям вспомогательных технологий приходится гадать, какие поля образуют тематический блок.
<fieldset class="contact-form__fieldset">
<legend class="contact-form__legend">Ваши личные данные</legend>
<div class="contact-form__group">
<label for="fname-demo" class="contact-form__label">
<span class="contact-form__error-prefix" hidden>Ошибка: </span>
Имя*
</label>
<input
type="text"
id="fname-demo"
name="fname"
required
aria-required="true"
aria-invalid="false"
aria-describedby="fname-error-demo"
class="contact-form__input"
/>
<div id="fname-error-demo" class="contact-form__error-message" hidden></div>
</div>
<div class="contact-form__group">
<label for="lname-demo" class="contact-form__label">
<span class="contact-form__error-prefix" hidden>Ошибка: </span>
Фамилия*
</label>
<input
type="text"
id="lname-demo"
name="lname"
required
aria-required="true"
aria-invalid="false"
aria-describedby="lname-error-demo"
class="contact-form__input"
/>
<div id="lname-error-demo" class="contact-form__error-message" hidden></div>
</div>
</fieldset>
<fieldset
class="contact-form__fieldset"
aria-describedby="contactMethod-group-error-demo"
>
<legend id="contactMethod-legend" class="contact-form__legend">
<span class="contact-form__error-prefix" hidden>Ошибка: </span>
Предпочтительный способ связи*
</legend>
<div class="contact-form__radio-group">
<label for="contact-pref-email-demo" class="contact-form__radio-label">
<input
type="radio"
id="contact-pref-email-demo"
name="contactMethod"
value="email"
required
aria-required="true"
class="contact-form__radio-input"
/>
По электронной почте
</label>
<label for="contact-pref-phone-demo" class="contact-form__radio-label">
<input
type="radio"
id="contact-pref-phone-demo"
name="contactMethod"
value="phone"
required
aria-required="true"
class="contact-form__radio-input"
/>
По телефону
</label>
</div>
<div
id="contactMethod-group-error-demo"
class="contact-form__error-message"
hidden
></div>
</fieldset>
Вспомогательные тексты и помощь при вводе
Иногда полям требуются дополнительные пояснения, например, о конкретном формате. Эти вспомогательные тексты должны быть программно связаны с полем.
Для этого вспомогательный текст получает id, а элемент <input> получает атрибут aria-describedby, который ссылается на этот id [14]. Скринридер сначала считывает метку, а затем вспомогательный текст.
Чтобы еще больше упростить ввод, Вы должны использовать атрибут autocomplete [15]. Он позволяет браузеру автоматически предлагать известные данные (имя, электронную почту и т. д.). Это не только удобно, но и является явным требованием [16].
<div class="contact-form__group">
<label for="phone-demo" class="contact-form__label">
<span class="contact-form__error-prefix" hidden>Ошибка: </span>
Номер мобильного телефона (Необязательно)
</label>
<input
type="tel"
id="phone-demo"
name="phone"
autocomplete="tel"
aria-invalid="false"
aria-describedby="phone-hint-demo phone-error-demo"
class="contact-form__input"
placeholder="напр.: 01701234567"
/>
<span id="phone-hint-demo" class="contact-form__help-text">
Для возможных обратных вопросов. Только действительные немецкие номера
мобильных телефонов.
</span>
<div id="phone-error-demo" class="contact-form__error-message" hidden></div>
</div>
<div class="contact-form__group">
<label for="email-demo" class="contact-form__label">
<span class="contact-form__error-prefix" hidden>Ошибка: </span>
Адрес электронной почты*
</label>
<input
type="email"
id="email-demo"
name="email"
autocomplete="email"
required
aria-required="true"
aria-invalid="false"
aria-describedby="email-hint-demo email-error-demo"
class="contact-form__input"
/>
<span id="email-hint-demo" class="contact-form__help-text">
Действительный адрес электронной почты (напр. max.mustermann@example.com).
</span>
<div id="email-error-demo" class="contact-form__error-message" hidden></div>
</div>
Обработка ошибок и валидация
Ошибки случаются. Доступная форма точно направляет пользователя к исправлению [18]. Стандартная валидация браузера (которую мы отключили с помощью novalidate) не подходит для этого, так как ее сообщения часто не фокусируемы или не воспринимаются скринридерами [19].
Наш подход состоит из трех частей:
- Сводка ошибок:
<div>в начале формы, который перечисляет все ошибки [6]. Это помогает пользователям быстро увидеть, что нужно исправить, и позволяет перейти непосредственно к ошибочным полям [20]. - Встроенные ошибки: Текстовое сообщение непосредственно под ошибочным полем, чтобы предоставить пользователю подробную информацию [21]. Само поле ввода помечается с помощью
aria-invalid="true", чтобы скринридеры могли распознать ошибку [22]. - Управление фокусом: При возникновении ошибки фокус активно устанавливается на сводку ошибок с помощью JavaScript.
- Мы настраиваем
<label>(или<legend>) каждого ошибочного поля, добавляя префикс "Ошибка: " . Это решающее значение для выполнения критерия успеха WCAG 1.4.1 [23]. Если бы мы изменили только цвет, ошибка не была бы распознана пользователями с дальтонизмом.
На Рисунке 4.1 Вы видите логику валидации и обработки ошибок, которую мы будем реализовывать далее.
Открыть текстовое описание для "Логика доступной обработки ошибок"
Эта блок-схема показывает процесс валидации:
- Пользователь нажимает на «Отправить».
- JavaScript предотвращает стандартную отправку.
- Функция
resetErrors()сбрасывает все старые сообщения об ошибках. - Функция
validateForm()проверяет все поля. - Принятие решения проверяет: «Ошибка найдена?».
- Если Да: Функция
showErrorSummary()заполняет сводку ошибок,showInlineError()помечает поля, и фокус устанавливается на сводку ошибок с помощьюerrorSummary.focus(). - Если Нет: Форма отправляется.
Теперь мы реализуем логику и HTML-структуру для обработки ошибок.
- HTML для сводки ошибок
- JavaScript для валидации
Сводка ошибок должна быть подготовлена в HTML. Сводка получает role="alert", чтобы скринридеры сразу же считывали ее при отображении [25], и tabindex="-1", чтобы мы могли сфокусировать ее с помощью JavaScript [26].
<div
id="error-summary-demo"
class="contact-form__error-summary"
role="alert"
tabindex="-1"
hidden
aria-labelledby="error-summary-title-demo"
>
<h2 id="error-summary-title-demo">Ваши данные содержат ошибки</h2>
<p id="error-summary-intro"></p>
<ul class="contact-form__error-summary-list"></ul>
</div>
<!-- Поля формы -->
В этом примере сводка ошибок сначала скрыта с помощью hidden и aria-hidden="true". Как только возникает ошибка, она становится видимой с помощью JavaScript.
Скрипт перехватывает событие submit, валидирует поля и управляет отображением ошибок.
Важны для доступности здесь две вещи:
showErrorSummary(errors): Создает список ошибок со ссылками, которые ведут непосредственно к ошибочному полю. Это важно для навигации с клавиатуры [20].showInlineError(inputId, message): Устанавливает атрибутaria-invalid="true"на ошибочном<input>[22] и отображает встроенное сообщение.
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('contact-form-demo');
const fnameInput = document.getElementById('fname-demo');
const errorSummary = document.getElementById('error-summary-demo');
const errorSummaryList = errorSummary.querySelector(
'.contact-form__error-summary-list'
);
const errorSummaryIntro = document.getElementById('error-summary-intro');
// --- Определения регулярных выражений ---
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
// --- Конфигурация полей (упрощено для сниппета) ---
// Оригинальный скрипт использует объект "fieldConfig",
// чтобы управлять всеми DOM-элементами (Inputs, Labels, Error-Divs).
// Здесь для простоты мы обращаемся к ним напрямую.
form.addEventListener('submit', (event) => {
event.preventDefault();
resetErrors(); // Сбросить старые ошибки
const newErrors = validateForm(); // Валидировать форму
if (Object.keys(newErrors).length > 0) {
displayErrors(newErrors); // Отобразить новые ошибки
errorSummary.focus(); // Установить фокус на сводку
} else {
// Форма успешно отправлена
console.log('Форма валидна.');
// (Логика будет добавлена в следующем разделе)
}
});
/**
* Считывает форму и возвращает объект ошибок.
*/
function validateForm() {
const newErrors = {};
// (Здесь вставить логику для всех полей)
if (fnameInput.value.trim() === '') {
newErrors.fname = 'Пожалуйста, укажите Ваше имя.';
}
// ...
const emailInput = document.getElementById('email-demo');
if (emailInput.value.trim() === '') {
newErrors.email = 'Пожалуйста, укажите Ваш адрес электронной почты.';
} else if (!EMAIL_REGEX.test(emailInput.value)) {
newErrors.email =
'Пожалуйста, введите действительный адрес электронной почты.';
}
// ...
return newErrors;
}
/**
* Показывает все ошибки в сводке и встроенно.
*/
function displayErrors(currentErrors) {
errorSummaryList.innerHTML = ''; // Удалить старые ссылки на ошибки
const errorEntries = Object.entries(currentErrors);
errorSummaryIntro.textContent = `Пожалуйста, исправьте следующие ${errorEntries.length} ошибок:`;
errorEntries.forEach(([key, message]) => {
// ID поля (например, 'fname-demo')
const fieldId = key + '-demo';
// Показать встроенную ошибку
const errorElement = document.getElementById(fieldId + '-error');
if (errorElement) {
errorElement.textContent = message;
errorElement.hidden = false;
}
// Найти и пометить Label/Legend
const labelElement =
document.querySelector(`label[for="${fieldId}"]`) ||
document.getElementById(key + '-legend'); // Для fieldsets
if (labelElement) {
labelElement.classList.add(
key === 'contactMethod'
? 'contact-form__legend--error'
: 'contact-form__label--error'
);
// Отобразить префикс ошибки
labelElement
.querySelector('.contact-form__error-prefix')
.removeAttribute('hidden');
}
// Пометить Input как 'aria-invalid'
const inputElement = document.getElementById(fieldId);
if (inputElement) {
inputElement.setAttribute('aria-invalid', 'true');
}
// Добавить ссылку в сводку ошибок
const li = document.createElement('li');
const a = document.createElement('a');
a.href = `#${fieldId}`; // Ссылка на Input-ID
const labelText =
labelElement?.dataset.errorsummaryLabel ||
labelElement?.textContent
.replace('*', '')
.replace('Ошибка:', '')
.trim() ||
key;
a.textContent = `${labelText}: ${message}`;
li.appendChild(a);
errorSummaryList.appendChild(li);
});
errorSummary.hidden = false;
}
/**
* Сбрасывает все индикаторы ошибок в форме.
*/
function resetErrors() {
errorSummary.hidden = true;
errorSummaryList.innerHTML = '';
errorSummaryIntro.textContent = '';
// Сбросить все встроенные ошибки и атрибуты
form.querySelectorAll('.contact-form__error-message').forEach((el) => {
el.hidden = true;
el.textContent = '';
});
form.querySelectorAll('[aria-invalid="true"]').forEach((el) => {
el.setAttribute('aria-invalid', 'false');
});
form
.querySelectorAll(
'.contact-form__label--error, .contact-form__legend--error'
)
.forEach((el) => {
el.classList.remove(
'contact-form__label--error',
'contact-form__legend--error'
);
});
form.querySelectorAll('.contact-form__error-prefix').forEach((el) => {
el.setAttribute('hidden', 'true');
});
}
});
Таким образом, доступная обработка ошибок реализована!
Доступное оформление статуса отправки (загрузка)
После того как пользователь нажимает «Отправить» и форма валидна, начинается процесс отправки. Этот сетевой запрос занимает некоторое время. Если Вы не предоставите обратной связи, пользователь не будет уверен:
- «Что-то произошло?»
- «Мне нужно нажать еще раз?»
Этот неопределенный «промежуток» мы должны преодолеть доступным способом. Лучший метод — это деактивировать кнопку, чтобы предотвратить двойную отправку [27], и сообщить о статусе «Занято».
Для этого мы настроим блок else в нашем submit-Event-Listener.
// ... в submit-Event-Listener ...
// ... (Определения переменных)
const submitButton = form.querySelector('button[type="submit"]');
if (Object.keys(newErrors).length > 0) {
// ... (Обработка ошибок, как выше) ...
} else {
// НЕТ ОШИБОК: Отправить форму
// 1. Установить статус UI на 'submitting'
// Эта функция управляет кнопкой, спиннером и живой областью
updateUIforStatusChange('submitting');
// 2. Здесь находится Ваша реальная логика отправки (напр. fetch)
// В этом примере мы ее имитируем:
simulateFormSend() // Эта функция возвращает Promise
.then((response) => {
// Обработать случай успеха
updateUIforStatusChange('success');
})
.catch((error) => {
// Обработать случай ошибки
updateUIforStatusChange('error');
});
}
// ...
/**
* Имитирует отправку (длится 3 сек.)
*/
function simulateFormSend() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ success: true });
}, 3000);
});
}
Для объявления статуса (statusRegion.textContent = ...) нам нужна живая область. Вы можете разместить ее непосредственно рядом с Вашей сводкой ошибок в HTML. Важно использовать role="status" и aria-live="polite", чтобы объявление не прерывало текущее считывание скринридером [28].
<div
id="form-status-message"
role="status"
aria-live="polite"
class="visually-hidden"
>
<!-- Здесь будет отображаться статус -->
</div>
Статус после отправки (Успех/Ошибка)
После процесса загрузки есть два результата: успех (HTTP 200) или ошибка (например, серверная ошибка, HTTP 500). Вы должны недвусмысленно сообщить об обоих пользователю. Здесь есть два распространенных, доступных сценария.
- Сценарий 1: Перенаправление на страницу подтверждения (Рекомендуется)
- Сценарий 2: Отображение статуса прямо в форме (без перенаправления)
Это самый надежный и простой метод.
- При успехе: Перенаправьте пользователя на страницу «Спасибо» (например,
/contact/thanks). - При ошибке: Перенаправьте пользователя на страницу «Ошибка» (например,
/contact/error).
Вот пример перенаправления при успехе в handleSuccess():
function handleSuccess(response) {
// 1. Перенаправление при успехе
window.location.href = '/contact/thanks';
}
Чтобы это было доступно, новая страница должна:
- Иметь четкий, описательный тег
<title>(например, «Спасибо за Ваше сообщение - Мой веб-сайт»). - Иметь четкий заголовок
<h1>(например, «Большое спасибо!»). - Фокус автоматически устанавливается на начало документа при загрузке новой страницы, в результате чего скринридер считывает новый заголовок страницы и
<h1>.
Вам не нужны aria-live или управление фокусом, так как смена страницы в браузере уже четко сообщает об изменении контекста.
Если Вы не хотите перенаправления, сообщение о статусе должно быть активно доведено до пользователя. Мы скрываем форму и показываем сообщение о статусе, которое получает фокус.
Очень важно: Это сообщение должно содержать кнопку для сброса формы. Иначе пользователь застрянет в тупике.
<div
id="form-final-status"
class="contact-form__final-status"
role="alert"
tabindex="-1"
hidden
>
<div id="final-status-success" hidden>
<h2>Большое спасибо!</h2>
<p>Ваше сообщение было успешно отправлено. (Это демо).</p>
<button type="button" class="contact-form__reset-button">
Заполнить снова
</button>
</div>
<div id="final-status-error" hidden>
<h2>Ошибка при отправке</h2>
<p>К сожалению, произошла ошибка. Пожалуйста, попробуйте снова.</p>
<button type="button" class="contact-form__reset-button">
Попробовать снова
</button>
</div>
</div>
// --- Эти элементы должны быть доступны глобально в скрипте ---
const form = document.getElementById('contact-form-demo');
const finalStatus = document.getElementById('form-final-status');
const successView = document.getElementById('final-status-success');
const errorView = document.getElementById('final-status-error');
const statusMessage = document.getElementById('form-status-message');
const submitButton = form.querySelector('button[type="submit"]');
const submitButtonText = submitButton.querySelector(
'.contact-form__submit-text'
);
const spinner = submitButton.querySelector('.contact-form__spinner');
const resetButtons = document.querySelectorAll('.contact-form__reset-button');
const fnameInput = document.getElementById('fname-demo'); // Первое поле
// Глобальная переменная статуса
let status = 'idle';
/**
* Обновляет весь пользовательский интерфейс формы на основе статуса.
* Эта функция вызывается слушателем 'submit'.
*/
function updateUIforStatusChange(newStatus) {
status = newStatus;
// --- Состояние загрузки (Submitting) ---
const isSubmitting = status === 'submitting';
submitButton.disabled = isSubmitting;
spinner.hidden = !isSubmitting;
submitButtonText.textContent = isSubmitting
? 'Отправляется...'
: 'Отправить сообщение';
statusMessage.textContent = isSubmitting ? 'Форма отправляется...' : '';
// --- Финальные состояния (Успех или Серверная ошибка) ---
const isSuccess = status === 'success';
const isError = status === 'error';
const isFinal = isSuccess || isError;
// Скрыть форму, показать блок статуса
form.hidden = isFinal;
finalStatus.hidden = !isFinal;
if (isFinal) {
// Показать правильный внутренний блок (Успех против Ошибки)
successView.hidden = !isSuccess;
errorView.hidden = !isError;
// Установить CSS-модификатор для стиля
finalStatus.classList.toggle(
'contact-form__final-status--success',
isSuccess
);
finalStatus.classList.toggle('contact-form__final-status--error', isError);
// ВАЖНО: Установить фокус на блок статуса!
finalStatus.focus();
} else {
// Удалить CSS-модификатор, если мы сбрасываем
finalStatus.classList.remove(
'contact-form__final-status--success',
'contact-form__final-status--error'
);
}
}
/**
* Event-Listener для кнопок сброса (в блоке Успех/Ошибка).
*/
resetButtons.forEach((button) => {
button.addEventListener('click', () => {
handleReset();
});
});
/**
* Сбрасывает всю форму в исходное состояние.
*/
function handleReset() {
form.reset(); // Собственный метод сброса формы HTML
resetErrors(); // Наша существующая функция сброса ошибок
updateUIforStatusChange('idle'); // Сбросить UI
// ВАЖНО: Сбросить фокус на первое поле!
fnameInput.focus();
}
Устанавливая фокус на контейнер role="alert", мы заставляем скринридер немедленно считывать сообщение об успехе или ошибке. Процесс handleReset() гарантирует, что пользователь (особенно использующий клавиатуру и скринридер) имеет четкий путь назад к началу.
Вывод
Создание доступной формы — это не магия, а добросовестное мастерство. Это требует тщательности и понимания того, как разные люди взаимодействуют с веб-пространством.
Используя семантический HTML (label, fieldset), создавая четкие структуры и внедряя устойчивое к ошибкам, поддерживающее руководство пользователя, Вы не только выполняете законодательные требования EAA [1]. Вы создаете лучший, более удобный для пользователя продукт для всех Ваших пользователей [29]. Показанные здесь методы являются универсальными и представляют собой решающий шаг к по-настоящему инклюзивному Интернету.
Сводный чек-лист всех пунктов из этого руководства Вы найдете в моем Чек-листе по формам для веб-разработчиков.
Часто задаваемые вопросы (FAQ) о доступных формах
Почему нужно использовать novalidate в теге <form>?
Атрибут novalidate отключает стандартные сообщения об ошибках браузера. Они часто недоступны (например, видны только как небольшие всплывающие подсказки, которые быстро исчезают) и не имеют единого дизайна [19]. С помощью novalidate мы берем на себя полный контроль и можем реализовать надежную, доступную для всех обработку ошибок с помощью JavaScript.
Разве красной рамки недостаточно для сообщений об ошибках?
Нет. Красная рамка — это чисто визуальная информация, которую не могут воспринять пользователи с нарушениями зрения (например, дальтонизмом) или при использовании скринридера [23]. Доступная обработка ошибок требует программных состояний (таких как aria-invalid="true") [22] и текстовых альтернатив, которые могут быть считаны вспомогательными технологиями [18].
Я должен соблюдать WCAG 2.1 или 2.2?
EAA ссылается на WCAG 2.1 [30] через европейский стандарт EN 301 549 [31]. Однако WCAG 2.2 [32] является более новым, рекомендуемым стандартом. Ориентация на WCAG 2.2 является проверенной практикой (Best Practice), поскольку она ориентирована на будущее и полностью охватывает требования 2.1. Больше информации об этом Вы найдете в моем разделе "Законодательство в области цифровой доступности".
В чем разница между required и aria-required="true"?
required — это атрибут HTML5, который сообщает браузеру, что поле должно быть заполнено (и вызывает нативную валидацию, если отсутствует novalidate). aria-required="true" — это ARIA-атрибут, который явно и наиболее надежным образом сообщает вспомогательным технологиям (таким как скринридеры), что поле является обязательным. Лучше всего использовать оба для обеспечения максимальной совместимости и семантической ясности, поскольку в некоторых случаях required сам по себе неверно интерпретируется всеми технологиями или ARIA не поддерживается.
Что насчет размера области нажатия кнопок (Target Size)?
Важным нововведением в WCAG 2.2 является критерий 2.5.8 Размер цели (Минимум) (Уровень AA) [33]. Он гласит, что интерактивные элементы (кнопки, ссылки, радиокнопки, чекбоксы) не должны быть меньше 24x24 CSS-пикселей.
Это критически важно для пользователей с моторными нарушениями, например, на сенсорных устройствах или при дрожании рук, так как это уменьшает вероятность случайного нажатия на неправильный элемент. При работе с CSS убедитесь, что Ваши цели клика (особенно часто слишком маленькие радиокнопки и чекбоксы) соответствуют этому требованию.
Отказ от ответственности
Содержание этого руководства предназначено исключительно для информационных целей и не является юридической консультацией. Хотя я стремлюсь предоставлять точную и актуальную информацию, я не несу ответственности за полноту, правильность или актуальность предоставленной информации. Соблюдение стандартов доступности может варьироваться в зависимости от конкретного контекста и сценария использования. Рекомендуется получить профессиональную консультацию при внедрении доступных форм и проводить регулярные тесты с реальными пользователями, чтобы убедиться в выполнении требований руководства WCAG 2.2. Я не несу ответственности за ущерб или убытки, возникшие в результате использования или доверия к информации, содержащейся в данном руководстве.