Поддержка экранных считывателей в масках ввода — один из наиболее сложных аспектов фронтенд-разработки, поскольку такие библиотеки, как Cleave.js, изменяют отображаемое значение поля без явного изменения пользовательского намерения. Это создает разрыв между тем, что видит пользователь, и тем, что интерпретирует assistive technology.
Cleave.js по своей природе не является библиотекой доступности, однако может быть корректно интегрирован в доступные интерфейсы при соблюдении ряда принципов, касающихся ARIA-атрибутов, синхронизации состояния и управления вводом.
Screen readers работают с DOM-значением input-элемента и событиями,
такими как input, change, focus,
blur. Cleave.js перехватывает ввод и модифицирует значение
поля, что приводит к нескольким особенностям:
Для screen reader это выглядит как постоянное обновление значения поля, что может восприниматься как “шум” или повторяющееся чтение.
Ключевая проблема заключается в том, что screen reader озвучивает уже отформатированное значение, а не «сырое» пользовательское представление.
Пример:
Пользователь вводит:
79991234567
Cleave.js отображает:
+7 (999) 123-45-67
Screen reader может озвучивать каждое изменение как:
Это приводит к перегрузке восприятия, особенно при быстром наборе.
Cleave.js не добавляет ARIA-атрибуты автоматически, поэтому разработчик должен управлять ими вручную.
Для корректного описания поля важно явно задавать смысл вводимого значения:
<input
id="phone"
type="tel"
aria-label="Номер телефона"
aria-describedby="phone-help"
/>
<div id="phone-help">
Введите номер в международном формате
</div>
Важно учитывать, что screen reader будет озвучивать уже форматированное значение, поэтому описание должно объяснять не формат, а цель поля.
Cleave.js обновляет значение input на каждое изменение. Для screen reader это может приводить к многократному озвучиванию одного и того же поля.
Решение заключается в минимизации влияния промежуточных изменений.
Прямое применение aria-live к input нежелательно, но
может использоваться для внешних отображений:
<input id="card" type="text" aria-label="Номер карты" />
<div aria-live="polite" id="card-display"></div>
const input = document.getElementById('card');
const display = document.getElementById('card-display');
const cleave = new Cleave(input, {
creditCard: true,
onValueChanged: function (e) {
display.textContent = e.target.value;
}
});
Такой подход отделяет визуальное форматирование input от озвучиваемого текста.
Один из наиболее стабильных подходов к доступности — разделение представлений:
<input id="phone" type="tel" aria-label="Телефон" />
<input
type="hidden"
id="phone-raw"
name="phone_raw"
/>
const input = document.getElementById('phone');
const hidden = document.getElementById('phone-raw');
const cleave = new Cleave(input, {
phone: true,
phoneRegionCode: 'RU',
onValueChanged: function (e) {
hidden.value = e.target.rawValue;
}
});
Screen readers взаимодействуют только с визуальным input, но форма отправляет чистое значение.
В некоторых интерфейсах важно снизить количество перерисовок, чтобы screen reader не реагировал на каждое изменение.
Подходы:
let timeout;
onValueChanged: function (e) {
clearTimeout(timeout);
timeout = setTimeout(() => {
display.textContent = e.target.value;
}, 200);
}
Это уменьшает частоту обновлений, которые могут быть озвучены assistive tools.
aria-hidden для декоративных элементовЕсли Cleave.js используется вместе с визуальными масками:
<span aria-hidden="true" id="formatted-value"></span>
Это предотвращает двойное чтение одного и того же значения.
Screen reader всегда работает в связке с клавиатурой, поэтому поведение каретки критично.
Cleave.js иногда изменяет позицию курсора после вставки форматирующих символов. Это может нарушать привычную модель навигации.
Рекомендации:
onValueChangedkeydown, если
возможноНекоторые screen readers воспринимают обновление value как новое событие и повторно озвучивают поле.
Это особенно заметно при:
Использование стабилизации значения:
let lastValue = '';
onValueChanged: function (e) {
if (e.target.value === lastValue) return;
lastValue = e.target.value;
}
При использовании Cleave.js вместе с валидацией важно корректно синхронизировать состояние ошибки.
<input id="card" aria-invalid="false" aria-describedby="card-error" />
<div id="card-error" role="alert"></div>
function validate(value) {
return value.replace(/\s/g, '').length === 16;
}
onValueChanged: function (e) {
const isValid = validate(e.target.rawValue);
input.setAttribute('aria-invalid', String(!isValid));
error.textContent = isValid ? '' : 'Неверный номер карты';
}
Screen readers должны получать только итоговое состояние, а не промежуточные изменения.
Browser autofill может вставлять значение уже в форматированное поле, после чего Cleave.js перерасчитывает его.
Это вызывает резкое обновление DOM, которое screen reader может озвучить как новое поле.
Подход:
onChange после autofillПри корректной настройке Cleave.js остаётся совместимым с:
Однако поведение зависит от частоты обновления DOM и количества форматирующих символов.
Ключевая цель — не изменять семантику поля, а только его визуальное представление.
Наиболее устойчивый подход к screen reader совместимости заключается в следующей архитектуре:
Такая модель снижает шум в озвучивании и сохраняет предсказуемость интерфейса для assistive технологий.