Поддержка вспомогательных технологий в числовых инпутах с автоматическим форматированием требует аккуратного баланса между визуальным представлением данных и их семантической доступностью для скрин-ридеров. Библиотека AutoNumeric, работающая с форматированием валют, процентов и чисел, вносит дополнительный слой представления, который может конфликтовать с тем, как экранные читалки интерпретируют значение поля ввода.
Ключевая проблема заключается в том, что визуально корректное число,
содержащее разделители тысяч, символы валют и локализованные форматы, не
всегда соответствует удобному для озвучивания представлению. Например,
строка 1,234,567.89 € может быть прочитана неоднозначно или
фрагментировано, в зависимости от движка скрин-ридера.
Базовый HTML-элемент input type="text" или
input type="tel" не несёт числовой семантики. При
подключении AutoNumeric он остаётся текстовым, но его содержимое
динамически форматируется.
Для корректного взаимодействия со скрин-ридерами используется комбинация следующих подходов:
aria-valuenow — числовое значение без
форматированияaria-valuetext — человеко-читаемое представление
числаrole="spinbutton" — указание на изменяемое числовое
полеaria-live — уведомления об изменении значенияAutoNumeric в стандартной конфигурации не навязывает ARIA-атрибуты, поэтому их настройка выполняется на уровне интеграционного слоя.
Основная стратегия поддержки скрин-ридеров заключается в разделении двух представлений одного значения:
В DOM это достигается синхронизацией AutoNumeric-инстанса с атрибутами элемента:
const anElement = new AutoNumeric(inputElement, {
digitGroupSeparator: ',',
decimalCharacter: '.',
currencySymbol: '€ ',
modifyValueOnWheel: false,
});
После каждой модификации значения производится обновление ARIA-атрибутов:
function syncAriaState(el, numericValue) {
el.setAttribute('aria-valuenow', numericValue);
const formattedText = new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR',
}).format(numericValue);
el.setAttribute('aria-valuetext', formattedText);
}
Экранные ридеры интерпретируют содержимое input-элемента буквально. При наличии разделителей тысяч и символов валют возникают следующие эффекты:
Особенно заметны проблемы при использовании нестандартных разделителей, например пробелов как группировочного символа.
Для устранения этих проблем применяется подход с явным указанием
aria-valuetext, который переопределяет текстовое
представление:
inputElement.setAttribute('role', 'spinbutton');
inputElement.setAttribute('aria-valuenow', '1234567.89');
inputElement.setAttribute(
'aria-valuetext',
'один миллион двести тридцать четыре тысячи пятьсот шестьдесят семь евро и восемьдесят девять центов'
);
В реальных системах текстовая конвертация может выполняться через локализованный числительный модуль.
AutoNumeric изменяет значение поля в ответ на пользовательский ввод, вставку, удаление и программные обновления. Скрин-ридеры не всегда отслеживают такие изменения без дополнительной сигнализации.
Использование aria-live позволяет объявлять
изменения:
aria-live="polite" — ненавязчивое объявлениеaria-live="assertive" — немедленное прерывание текущего
чтенияПример интеграции:
inputElement.setAttribute('aria-live', 'polite');
function announceChange(value) {
liveRegion.textContent = `Значение обновлено: ${value}`;
}
Часто используется отдельный скрытый live-region контейнер:
<div id="an-live-region" aria-live="polite" class="sr-only"></div>
.sr-only {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
}
При использовании AutoNumeric поле может быть семантически приближено
к spinbutton. Это важно для корректной работы клавиш:
ARIA-атрибуты должны отражать диапазон:
inputElement.setAttribute('role', 'spinbutton');
inputElement.setAttribute('aria-valuemin', '0');
inputElement.setAttribute('aria-valuemax', '1000000');
inputElement.setAttribute('aria-valuenow', '2500');
AutoNumeric может управлять диапазонами через конфигурацию, но ARIA слой остаётся отдельной ответственностью.
Основная сложность возникает при синхронизации трёх представлений:
Несогласованность приводит к следующим ошибкам:
aria-valuenow не соответствует отображаемому числуaria-valuetext не обновляется после программного
измененияРешение заключается в централизованной функции синхронизации:
function updateAllStates(an, el) {
const numericValue = an.getNumber();
el.setAttribute('aria-valuenow', numericValue);
el.setAttribute(
'aria-valuetext',
new Intl.NumberFormat('ru-RU', {
style: 'decimal',
}).format(numericValue)
);
}
Эта функция вызывается:
Разные локали требуют различной стратегии формирования
aria-valuetext. В европейских локалях акцент делается на
группировке тысяч, в восточноазиатских — на позиционной структуре
числа.
Пример локализованного преобразования:
function formatForScreenReader(value, locale) {
return new Intl.NumberFormat(locale, {
notation: 'standard',
}).format(value);
}
При этом aria-valuenow всегда остаётся в нейтральном
числовом формате (без разделителей и локализации).
Иногда требуется полностью исключить влияние визуального форматирования на озвучивание. В таких случаях применяется стратегия:
aria-valuetextaria-hidden на вспомогательных
визуальных элементахПример:
<input id="amount" type="text" />
<span aria-hidden="true" class="visual-format"></span>
Такой подход уменьшает расхождение между восприятием и озвучиванием.
Распространённая архитектура включает два поля:
<input id="amount-visible" type="text" />
<input id="amount-hidden" type="hidden" />
Синхронизация:
anElement.addEventListener('autoNumeric:rawValueModified', () => {
hiddenInput.value = anElement.getNumber();
syncAriaState(inputElement, anElement.getNumber());
});
Эта модель упрощает интеграцию с формами и серверной валидацией, одновременно повышая предсказуемость для вспомогательных технологий.
Ошибки валидации числового ввода должны быть доступны скрин-ридерам
через aria-describedby и live regions.
inputElement.setAttribute('aria-invalid', 'true');
inputElement.setAttribute('aria-describedby', 'amount-error');
<div id="amount-error" role="alert">
Значение выходит за допустимый диапазон
</div>
AutoNumeric может предотвращать некорректный ввод, но семантика ошибки остаётся задачей интеграционного слоя.
При высокой скорости ввода возможны рассинхронизации между:
Для устранения используется debounce синхронизации:
let t;
inputElement.addEventListener('input', () => {
clearTimeout(t);
t = setTimeout(() => {
updateAllStates(anElement, inputElement);
}, 50);
});
Это снижает вероятность гонок состояния и некорректных объявлений.
Корректная интеграция AutoNumeric с экранными ридерами опирается на разделение слоёв:
Такая модель обеспечивает стабильное поведение для вспомогательных технологий при сохранении богатого форматирования и локализации числовых значений.