Доступна форма - та, якою можна користуватися з клавіатури й зчитувачем екрана. Основа - правильні зв'язки між полями, мітками й повідомленнями.
Мітка для кожного поля. placeholder мітку не замінює: він зникає при введенні й погано читається.
<label htmlFor="email">Електронна адреса</label>
<input id="email" name="email" type="email" />
У JSX - htmlFor, а не for.
Унікальні id - через useId. Жорсткий id="email" ламається, якщо компонент поля використано на сторінці двічі. useId генерує стабільний унікальний id, однаковий на сервері й клієнті (без розбіжностей гідратації):
function TextField({ label, error, ...props }) {
const id = useId();
const errorId = `${id}-error`;
return (
<div>
<label htmlFor={id}>{label}</label>
<input
id={id}
aria-invalid={error ? true : undefined}
aria-describedby={error ? errorId : undefined}
{...props}
/>
{error && <p id={errorId} role="alert">{error}</p>}
</div>
);
}
aria-invalid- зчитувач оголосить, що поле з помилкою;aria-describedby- текст помилки прочитається разом з полем;role="alert"- повідомлення оголошується одразу при появі.
useId - не для ключів у списках: ключі мають походити з даних.
Інші правила:
- групи радіокнопок і чекбоксів - у
<fieldset>з<legend>; - фокус на першій помилці після невдалої відправки (React Hook Form робить це сам -
shouldFocusError); - не вимикати кнопку відправки «доки форма невалідна» - користувач не розуміє, що не так. Краще дозволити відправку й показати помилки;
- правильні типи полів (
type="email",inputMode="numeric",autoComplete="email") - правильна клавіатура на телефоні й автозаповнення; - стан відправки оголошувати текстом («Зберігаємо...»), а не лише спінером.
Перевірка: пройти форму лише клавіатурою (Tab, Enter, пробіл) і з увімкненим VoiceOver/NVDA.