Всё про доступность интерфейсов
7 subscribers
116 photos
193 links
Download Telegram
Pressable не становится кнопкой сам по себе

В свежем issue по assistant-ui заметили: 19 React Native action-компонентов рендерятся через Pressable без accessibilityRole. Для VoiceOver и TalkBack это может звучать не как кнопка, а как обычный generic element.

Если компонент ведёт себя как кнопка, роль лучше задавать в primitive/design-system слое, а не на каждом экране вручную.
В React Native есть неприятная ловушка: Pressable делает элемент фокусируемым, но сам по себе не говорит экранному диктору, что это кнопка.

В свежем issue по assistant-ui это поймали на мобильных примитивах библиотеки. В @assistant-ui/react-native 19 action-компонентов рендерятся через Pressable без accessibilityRole. В итоге VoiceOver и TalkBack могут объявлять такие элементы как обычный generic element, а не как кнопку.

Для зрячего пользователя это всё ещё выглядит как кнопка: иконка, зона нажатия, реакция на тап. Но для пользователя экранного диктора пропадает важная часть договора: что это за элемент и какое действие от него ждать.

Проблема особенно неприятна в интерфейсах с ассистентами и чатами. Там много маленьких действий: редактировать, скопировать, повторить, остановить, открыть меню. Если они звучат как безымянные или обычные элементы, пользователь начинает обследовать интерфейс наугад и тратить внимание на базовую навигацию вместо самой задачи.

В React Native Pressable не равен кнопке для доступности.

Если компонент ведёт себя как кнопка, ему нужен явный accessibilityRole="button". Лучше задавать это на уровне дизайн-системы или primitive-компонента, а не надеяться, что каждый экран не забудет роль вручную.

Минимальная проверка для команды:

• Взять все `Pressable`, `Touchable*` и кастомные action-компоненты.

• Проверить, есть ли у них роль, имя и состояние.

• Прогнать хотя бы основной сценарий с VoiceOver и TalkBack.

• Отдельно проверить маленькие icon-only действия: именно они чаще всего выглядят очевидно глазами и бессмысленно звучат голосом.

Кнопка без роли - это не мелкая семантическая придирка. Это ситуация, где интерфейс как будто говорит: “сюда можно нажать”, но не говорит, что именно перед тобой действие.

Источник: GitHub issue assistant-ui#6117
Представьте учебный сайт, где формы нормальные, модалка умеет возвращать фокус, есть live regions, но главный «кампус» живёт только под мышью.

Нашёлся свежий issue по Pembroke.Academy: аудит прямо пишет, что «the forms are accessible; the campus is not usable without a pointer». Там почти нет фокус-индикаторов, hall nameplates сделаны как div, а здания и персонажи открываются только через pointer raycast. Клавиши камеры есть, но войти в зал или поговорить с персонажем без мыши нельзя.

Это хороший пример не про «добавьте aria-label». Здесь ломается сама механика интерфейса.

Кому мешает:

• пользователям клавиатуры и switch control;

• людям с моторными нарушениями, которым трудно точно целиться мышью;

• пользователям экранных дикторов, потому что путь по объектам мира не превращён в понятные кнопки/ссылки;

• людям с вестибулярной чувствительностью: в аудите отдельно отмечено, что часть движения мира, облаков и эффектов не закрыта через prefers-reduced-motion.

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

Для таких интерфейсов обычно нужен параллельный управляемый маршрут:

• фокусируемые объекты с нормальными именами;

• Enter/Space для действия;

• roving tabindex или список объектов рядом с картой;

• skip-link внутрь и наружу;

• видимый focus-visible;

• reduced motion для всего движущегося слоя, а не только для пары декоративных анимаций.

Иначе получается странная доступность: форма заявки доступна, а сам продукт, ради которого человек пришёл, нет.

Источник: P1: Accessibility - almost no focus indicators, and no keyboard path into the campus
Перетаскивание мышью - не единственный способ управлять расписанием

В FlowState завели issue про таймлайн расписания: блоки нужно не только двигать мышью, но и перемещать/изменять с клавиатуры.

Хороший минимум: стрелки для сдвига, клавиатурное изменение длительности и live region, который объявляет новое время.

Полный разбор - следующим сообщением.
Перетаскивание мышью - не единственный способ управлять расписанием

В FlowState завели issue про доступность таймлайна расписания: блоки на дневной оси нужно не только двигать мышью, но и перемещать/изменять с клавиатуры.

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

В issue сформулирован хороший минимум: сфокусированный блок можно сдвигать стрелками, например на 15 минут, менять длительность с клавиатуры, а новое время озвучивать через live region. То есть пользователь не должен угадывать: «сейчас 10:00-10:30 или уже 10:15-10:45?».

Это полезный тест для любых drag-and-drop интерфейсов: канбан, календарь, редактор таймлайна, сортировка карточек, настройка виджетов.

Если действие сделано через drag, рядом должен быть равный не-мышиный путь:

- фокус попадает на сам объект;
- есть понятные команды перемещения и изменения размера;
- после действия объявляется новое состояние;
- пользователь может отменить или проверить результат без зрения и без мыши.

Иначе это не «удобный визуальный редактор», а интерфейс, который часть людей может только рассматривать.
Когда экранному диктору вместо заголовка дают имя переменной

В Kibana нашли две похожие проблемы. Пользователь открывает flyout Upload SIEM rules или Upload Splunk dashboards, а VoiceOver объявляет не видимый заголовок, а технический id: rulesMigrationDataInputFlyoutTitle / dashboardsMigrationDataInputFlyoutTitle.

Визуально заголовок есть. Но для VoiceOver это окно с бессмысленным названием. Человек теряет ориентацию: что открылось, туда ли он попал, безопасно ли загружать файл.

Исправление: aria-labelledby у dialog/flyout должен ссылаться на элемент с реальным видимым заголовком, а не на внутренний ключ локализации или id.

Тест: откройте drawer/flyout с экранным диктором и послушайте первую фразу. Если она звучит не как заголовок на экране, сценарий начинается с путаницы.

Источник: elastic/kibana#285911, рядом #285917
Свежий пример из JLS: приложение может быть доступным в dev-запуске, но «немым» после установки, если в упакованный runtime не попал Java Access Bridge.

Ниже - полный разбор, почему доступность надо проверять на настоящем релизном инсталляторе, а не только в IDE.
В GitHub-issue по JLS поймали не баг в кнопках и не пропущенный aria-label, а более неприятную штуку: установленная Windows-версия Java-приложения может быть полностью «немой» для NVDA и JAWS.

Причина в сборке. Инсталлятор делает свой урезанный Java runtime через статический анализ зависимостей. А модуль jdk.accessibility, где лежит Java Access Bridge, туда сам не попадает: код приложения на него напрямую не ссылается. Плюс в поставке нет accessibility.properties, который включает мост.

В итоге визуально приложение запускается, функции со spoken feedback могут быть написаны, но экранный диктор к установленной версии просто не подключается. Для студента на Windows это разница между «скачал MSI и работаешь» и «ищи полный JDK, запускай jar руками, если вообще можешь».

Вывод для команд простой: доступность надо проверять не только в dev-запуске. Пройдите именно тот путь, который проходит пользователь: скачал релиз, поставил, открыл, включил NVDA/JAWS/VoiceOver/TalkBack и попробовал выполнить главную задачу.

Особенно это касается Electron, Java, Qt, Flutter desktop и любых приложений с упакованным runtime. Иногда доступность ломается не в интерфейсе, а в упаковке.
Лишняя кнопка тоже может ломать доступность.

В UI5 Web Components React завели issue про TabContainer: стрелка раскрытия рядом со вкладкой попадает в JAWS как отдельная кнопка More.

Визуально это маленькая стрелка. Но для screen reader она становится ещё одной командой в списке кнопок. Пользователь слышит лишний «More button» и должен угадывать, часть это вкладки или отдельное действие.

Проверка для команд: в вкладках, меню и split-button-паттернах ищите не только пропущенные подписи, но и лишний интерактивный мусор в дереве доступности.
Лишняя кнопка тоже может ломать доступность.

В UI5 Web Components React завели issue про TabContainer: у вкладки есть подпункты, рядом с ней визуально показывается стрелка раскрытия, и эта стрелка попадает в JAWS как отдельная кнопка More.

На экране это может выглядеть нормально: вкладка «General Information» и маленькая стрелка рядом. Но в JAWS Button List появляется ещё один отдельный элемент «More button menu». Пользователь слышит как будто две разные точки действия и должен угадывать, что это часть той же вкладки, а не самостоятельная команда.

Интересная деталь: в SAPUI5 reference похожая стрелка сделана декоративной: role="presentation" и aria-hidden="true". То есть screen reader видит саму вкладку и её состояние, но не техническую иконку рядом.

Вывод простой: доступность ломают не только невидимые кнопки. Иногда мешает наоборот лишний интерактивный мусор в дереве доступности.

Для вкладок, меню, раскрывающихся стрелок и split-button-паттернов стоит проверять не только Tab-порядок. Откройте список кнопок/ссылок в JAWS или NVDA и посмотрите, не появились ли там декоративные иконки, стрелки и внутренние «More», которые пользователь не должен выбирать отдельно.
Всё про доступность интерфейсов pinned «ВАЖНО: интересен и нужен ли вам этот канал?»
В VS Code есть хороший сигнал про доступность расширений.

В issue по Bookmarks незрячий пользователь описал простую вещь: он ставит закладку на строку, двигается дальше, возвращается назад - и экранный диктор не сообщает ни что закладка поставлена, ни что текущая строка уже с закладкой.

Визуально всё есть: значок в редакторе, список закладок, состояние строки. Но с JAWS, NVDA, VoiceOver или Narrator приходится лезть в отдельный список и проверять вручную. Для инструмента навигации это почти ломает смысл функции.

Мейнтейнер ответил, что VS Code пока не даёт расширениям публичный API для таких невизуальных объявлений и accessibility signals. То есть проблема не только в одном плагине, а в границе платформы.

Что проверять: если статус показан иконкой, цветом, gutter, badge или звуком, нужен второй канал. Пользователь должен узнать: действие сработало, состояние изменилось, текущий объект уже в этом состоянии.
Dictus iOS: функция включается drag-жестом, а под VoiceOver этот путь исчезает. Хороший пример, почему у красивого жеста всегда должна быть понятная альтернатива.
У Dictus iOS нашли хороший пример: функция вроде бы есть, но для VoiceOver её нет.

Smart Mode включается длинным нажатием на кнопку микрофона, потом перетаскиванием на строку в веере и отпусканием. В обычном сценарии это быстрый жест. Но VoiceOver перехватывает долгий drag, а строки веера ещё и не являются accessibility elements. В итоге незрячий пользователь не может выбрать режим диктовки вообще.

Это не “чуть неудобнее”. Это другой продукт: зрячий пользователь меняет режим прямо в клавиатуре, а пользователь VoiceOver остаётся без основного действия.

Что здесь важно для интерфейсов:

• жест с drag не должен быть единственным способом выполнить действие;

• для iOS стоит добавить accessibility actions / rotor actions или отдельное действие в списке режимов;

• кнопка микрофона должна иметь понятное имя, состояние и текущий выбранный режим;

• тестировать надо не “кнопка фокусируется”, а полный путь: найти микрофон, выбрать режим, начать диктовку, услышать состояние, отменить или изменить выбор.

Если интерфейс держится на красивом жесте, простой контрольный вопрос такой: что сделает человек, который управляет телефоном не пальцем по экрану, а VoiceOver, switch control или голосом?

Источник: getdictus/dictus-ios#397
Свежий issue в Linksys PrivacyGUI: карточку dashboard можно переставить с клавиатуры, и на экране всё выглядит успешно.

Но после повторного открытия страницы карточка возвращается назад. Мышиный drag сохраняется, а клавиатурный путь нет.

Главный тест для drag-and-drop альтернатив: не только «можно ли двигать без мыши», но и «сохранился ли результат после Save/Done и повторного открытия экрана».
Клавиатурный путь должен сохранять результат, а не только красиво двигать карточку

Свежий issue в Linksys PrivacyGUI: на dashboard можно переставить карточку с клавиатуры. Пользователь входит в режим редактирования, Tab-ом доходит до карточки, нажимает Space, стрелками двигает её, снова Space, потом Done.

На экране всё выглядит нормально: карточка переехала. Даже семантическая подпись обновляется: было Row 1, Column 0, стало Column 1.

Но после повторного открытия страницы карточка возвращается назад. Мышиный drag сохраняется, Optimize сохраняется, а именно клавиатурный путь не пишет новое расположение в хранилище.

Это неприятный класс багов. Формально доступность как будто есть: фокус есть, клавиши работают, состояние озвучивается. Но реальная задача не выполнена. Человек с моторными ограничениями, пользователь клавиатуры, switch control или голосового управления тратит время, настраивает свой dashboard и молча теряет результат.

Здесь практический тест простой: если вы делаете drag-and-drop альтернативу, проверяйте не только сам жест.

Нужно пройти весь цикл:

1. выбрать элемент без мыши;
2. переместить или изменить его;
3. получить понятное подтверждение;
4. нажать Save/Done;
5. открыть экран заново;
6. убедиться, что результат сохранился.

Иначе получается опасная иллюзия: интерфейс «доступен» ровно до момента, когда пользователь пытается сохранить свою работу.
Карта может быть недоступной не из-за карты

В GitHub появился свежий issue по DRE Visualizations: после релиза нужно проверить MapLibre-контролы. Там уже увеличили кнопки навигации и закрытия попапа до 44×44 CSS px для coarse pointer - для пальца, стилуса и других неточных способов ввода.

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

Что я бы проверяла:

1. Тач-контролы не меньше 44×44 px.
2. Большая зона не перекрывает текст в попапе.
3. Управление работает с клавиатуры.
4. Фокус видно.
5. Закрытие, масштаб и прокрутка страницы не конфликтуют.

Доступность карт - это не только alt-текст. Иногда это нормальный размер кнопки, чтобы человек мог реально нажать её, а не играть в пиксельную охоту.

Источник: DRE Visualizations issue #13
Reduced motion как настройка продукта

В ProgressRPG завели issue про отдельный переключатель «уменьшить движение» в настройках аккаунта.

Предложение аккуратное: при первом открытии взять системный prefers-reduced-motion как подсказку, но дальше хранить выбор внутри приложения и дать человеку переопределить его.

Это помогает людям с вестибулярной чувствительностью, ADHD, мигренями и перегрузом от анимаций. Плюс обычный случай: пользователь может не хотеть менять всю ОС из-за одной игры, где двигаются прогресс-бары и эффекты level-up.

Для интерфейса урок такой: не пытайтесь «вычислить screen reader». Надёжного браузерного API для этого нет, и это вопрос приватности. А reduced motion - явная настройка. Её можно уважать, показать в интерфейсе и проверить на декоративных анимациях.

Мини-тест: включили настройку - переходы, автоскролл, эффекты прогресса и победы стали спокойными, а смысл экрана не потерялся.
Highcharts: график закрыли, а accessibility-таймеры ещё говорят.

Свежий баг про отложенные объявления в aria-live: старый компонент не должен подмешивать сообщения в новый экран.

Полный разбор ниже.
Свежий баг в Highcharts: график закрыли, а accessibility-таймеры продолжают работать.

В issue от 30 августа описан сценарий: график с accessibility.announceNewData.enabled ставит отложенное объявление для screen reader. Потом компонент уничтожают, но два таймера не очищаются. Один позже может объявить новые данные, второй - записать текст в уже отсоединённый live-region.

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

Что проверять:

• при размонтировании виджета очищаются все setTimeout/setInterval;

aria-live не объявляет события от старого состояния;

• после закрытия модалки, вкладки или графика нет запоздалых озвучек;

• на это есть регрессионный тест.

Доступность ломается и от тишины, и от лишней речи. Старый компонент не должен продолжать говорить.

Источник: Highcharts issue #25117