Привет, меня зовут Вова Тимофеев, я менеджер технических проектов Yandex Cloud. В статье поделюсь тем, как мы делали сайт облачной платформы доступнее, сколько итераций прошли и какую роль в этом сыграл Gravity UI.
В основе доступности всех сервисов — то, насколько хорошо они поддерживают работу с программами экранного доступа (Screen reader). Через эти программы пользователи с ограничениями воспринимают интерфейс и взаимодействуют с ним.
Сайты — не исключение. И нам предстояло выяснить, насколько доступен Yandex Cloud для всех пользователей.
В Яндексе под доступностью мы подразумеваем то, что наши сервисы должны с комфортом использовать все, вне зависимости от временных или постоянных физических ограничений. Например, сейчас для незрячих пользователей адаптировано 16 сервисов Яндекса: Лавка, Go, Поиск, Браузер, Почта и другие. В работе над доступностью каждого сервиса помогает команда невизуального тестирования — и в кейсе, о котором расскажу в этой статье, без их помощи тоже не обошлось.
Спойлер: в результате тестирования обнаружилось несколько спорных моментов при работе с программами экранного доступа, которые превратились в рабочие задачи.
Но обо всём по порядку.
Всё началось с Gravity UI
Gravity UI — дизайн‑система и библиотека компонентов, на которой работает сайт Yandex Cloud и десятки других продуктов облака. Она выложена в опенсорс и доступна всем (нам приятно, что за последние полгода активность в community‑чате ощутимо выросла).
Что у нас есть:
набор базовых React‑компонентов;
библиотека‑конструктор для лендингов;
подробные гайды от дизайнеров по использованию компонентов;
библиотека в Figma;
набор из почти 600 готовых иконок;
ChartKit — пакет для визуализации данных;
Yagr — высокопроизводительный рендеринг графиков, основанный на uPlot;
i18n — пакет для локализации интерфейса;
другие полезные библиотеки.
В марте 2024 года вышло обновление ключевой библиотеки — 6-я версия UIKit. В ней обновился компонент List, появились поддержка параметра RTL во всех компонентах и пакет доработок a11y, улучшающих доступность.
Что нового в 6-й версии UIKit
-
Компонент List 2.0. В UIKit изначально был List, но в нём хотелось кое‑что доработать. При сборе запросов собрали список:
поддержка разных размеров и ширины;
иконка у элемента списка, разное количество и положение иконок;
поддержка стейтов;
разный контент в элементах списка (однострочный, многострочный или список пользователей);
поддержка разных видов разделителей и группировок.
Это существенные изменения, поэтому мы создали List 2.0. Пока он выходит в версии prestable, но рекомендуем пользователям переходить на него и приносить фидбэк.
-
RTL. Если ваши приложения или сайты должны отображаться на иврите, арабском и других языках с направлением письма справа налево, нужна поддержка RTL‑стандартов. При этом в RTL:
вставленное слово на латинице пишется слева направо;
цифры пишутся слева направо;
знаки препинания в арабском также пишутся слева направо и т. д.
Во всех компонентах мы поддержали параметр RTL. Чтобы под рукой был полный пример, сделали промостраницу на арабском. Посмотреть, как это реализовано, можно в исходном коде landing. Также примеры есть в storybook.
-
Доступность (a11y):
добавили в проект плагин eslint;
поддержали клавиатуру для clickable и closable состояния компонента Persona;
отключили onClick у 15 неинтерактивных компонент;
поддержали клавиатуру в компоненте SelectionTable.
Как пришли к улучшениям a11y
В этом помогла команда невизуального тестирования и её руководитель Анатолий Попко. На встрече Анатолий пошагово тестировал песочницу и сайт Gravity UI, чтобы понять, какие проблемы с доступностью сейчас есть.
Проверяли доступность компонентов, перемещаясь по сайту с помощью клавиатуры и специальных команд скринридеров.
Визуально это выглядело так:
После встречи у команды появились рабочие задачи, а в GitHub — три новых issue по базовым компонентам.
Подробнее о них.
В раскрывающемся списке второй уровень элементов не открывается с помощью клавиатуры, только через клик мышкой.
Непонятно, какой элемент в маркированном списке markdown выбран в компоненте Select.
Кнопки без текстовых меток, обозначенные только графически, воспроизводятся как просто «кнопка» или «радиокнопка». Этот баг встречается только на лендинге, сам компонент поддерживает aria‑label, но мы им не воспользовались.
В результате мы поняли, что, раз мы не нашли десятки замечаний, доступность библиотеки уже на неплохом уровне. Тестировать компоненты не в реальном контексте очень тяжело, из‑за этого мы решили начать проверку доступности готового продукта. Так нам удалось найти дополнительные улучшения a11y.
Доступность Yandex Cloud
Вдохновившись обновлением Gravity UI, мы решили протестировать на доступность наши сервисы: начать с сайта Yandex Cloud, а после распространить этот опыт и на другие интерфейсы.
Есть ряд стандартов, соблюдая которые можно добиться доступности. Но чтобы достоверно протестировать интерфейсы Yandex Cloud и лучше понять, насколько наш сайт удобен для всех, мы провели аудит.
Аудит доступности
Мы снова обратились к коллегам из команды невизуального тестирования и к Анатолию, чтобы вместе протестировать сайт, зафиксировать проблемы и забрать их на доработку. Всего было две итерации с разницей почти в месяц — тестирование и ретест.
В ходе тестирования зафиксировали целый пул правок, которые предстояло взять в работу.
На главной
Контрол поиска нуждался в переработке. В нашем интерфейсе компонент поиска реализован в виде зоны поиска с иконкой лупы. По клику на них открывается поле для ввода запроса. В прошлой реализации для скринридера это были независимые элементы, что путало незрячих пользователей.
В выборе языка мы использовали атрибут состояния «свёрнуто», хотя по сути это была кнопка выбора, а не выпадающий список. Не было подписи «язык», не хватало пробела в переходе «язык — регион».
В меню аккаунта не запирался фокус: при активации пользователь выходил за пределы пунктов меню.
Тег main на главной дублировался, поэтому предстояло один убрать.
Раздел с примерами: необходимо использовать вкладку вместо кнопки.
В карточке примера текст был убран в aria‑describedby, из‑за этого скринридер зачитывал текст однократно, что неудобно при изучении важной информации. Когда мы исследовали баг, мы поняли, что стоит сделать комплексный рефакторинг компонента Card и завели issue, в котором можно узнать детали изменений и поучаствовать в их обсуждении.
Навигация
Проблема с фокусом: при развороте верхнего уровня меню нужно перемещать фокус в подменю.
Было необходимо убрать TabIndex у верхнего уровня меню. Текущий вид приводил к двукратному перечислению всех пунктов меню.
Нужно было запереть клавиатурный фокус в навигации, если она развёрнута. Иначе можно вылететь из меню на саму страницу, а после не вернуться назад.
Footer
Yandex Cloud обёрнут списком. Заголовки списков ссылок в футере были завёрнуты в элемент списка, поэтому NVDA определял их как часть и дважды зачитывал один и тот же список пользователю.
Не хватало подписей для ссылок на AppStore, Google Play. В момент теста зачитывались фрагменты URL, из‑за этого пользователю ничего не было понятно.
Раздел «Блог»
Кнопки «Все темы» и «Все сервисы» не были соотнесены с кнопкой. Кнопки селектов не озвучивали их содержание.
Нужна была поддержка доступности в списках: она заключается в том, чтобы при открытии выпадающего списка фокус программ экранного доступа переключался на элементы этого списка. Дополнительно он должен поддерживать упрощённое перемещение между ними с помощью обычных стрелок — без использования каких‑либо сочетаний клавиш.
Статьи блога
В хлебных крошках не хватало элемента «Вы здесь».
Нужно было запереть фокус в диалог.
Не было названия у счётчика избранного. Счётчик представлял собой кнопку с иконкой и числом. Программы экранного доступа зачитывали число — без иконки не было понятно, что делает кнопка.
Все задачи мы собрали в эпик и приступили к работе. К части задач были созданы issue на GitHub.
О самых интересных кейсах расскажу подробнее.
Компонент Select
При тестировании мы обнаружили, что при клавиатурной навигации названия элементов списка не объявляются скринридером. Частично проблему удалось пофиксить с помощью атрибута aria-activedescendant, но не всё.
Вот какие проблемы сохранились
В Safari этот способ не работает, и пока нет понимания, как это решить.
-
Не всегда поддерживается фильтр поиска (на самом деле всегда, просто его поддержка не реализована). Обычно aria-activedescendant навешивается на главный элемент выпадающего списка и указывает на выбранный в списке элемент. Сейчас он вешается на кнопку, открывающую выпадающий список. При нажатии стрелок вверх или вниз мы меняем у кнопки значение aria-activedescendant на предыдущий или следующий элемент списка. Так программа экранного доступа может прочитать с выбранной кнопки, какой элемент сейчас выбран.
Проблема с фильтром поиска заключается в том, что на его поле ввода нет атрибута aria-activedescendant. Если человек сфокусировался на поле ввода фильтра и хочет туда что-то ввести, программа экранного доступа не может с него считать, какой элемент списка активный, и навигация по списку стрелками не работает.
Выбранные опции не отмечены, необходимо добавить на них атрибут, например aria-selected.
Issue с актуальными проблемами можно посмотреть тут.
Хлебные крошки
Хлебные крошки (Breadcrumbs) — это элемент навигации, который показывает путь пользователя по сайту. В нашем случае скринридер зачитывал весь путь, и было невозможно понять, в какой части сайта человек находится. Кроме того, весь компонент представлялся просто как набор ссылок.
Мы решили отойти от стандартов и решить эту задачу простым и оптимальным способом — повесив для скринридера тег «Вы здесь». В конечном итоге мы совместили стандартный подход и тег.
В процессе работы выяснилось: чтобы эта подпись зачитывалась, нужно навесить её на элемент, который программы экранного доступа не игнорируют. Проще было навесить подпись на конвенциональную структуру, чем придумывать способ её избежать. Тем не менее подпись всё ещё полезна: она помогает пользователю быстрее понять, что он сейчас слышит именно хлебные крошки.
Изображения без подписи
Некоторые изображения на сайте были расположены без подписей к ним, и программа экранного доступа зачитывала их как «Изображение». Эта информация не несёт для пользователя полезной информации и не даёт представления об интерфейсе, поэтому мы решили прятать изображения без подписей от скринридера.
Порядок текста в блоках
В нескольких местах на нашем сайте информация представлена в виде единого блока, например карточка мероприятия или карточка статьи в блоге.
Мы заметили, что скринридеры зачитывают информацию в порядке, которому не следуют зрячие пользователи. Для карточки мероприятия программа экранного доступа зачитывала данные так: статус регистрации, время, место и только потом заголовок и описание мероприятия.
Обычно пользователь не следует линейному пути: он смотрит на заголовок, потом — на подзаголовок и саму картинку. Проблема в том, что скринридер читает элементы в том порядке, в котором они расположены в DOM‑дереве страницы. Чтобы поправить порядок, нам пришлось его пересобрать.
Мы были удивлены, но в некоторых связках «операционная система — браузер» это не сработало. Например, в Mozilla Firefox на macOS проблема сохранилась, несмотря на изменение порядка в дереве страницы. Будем надеяться, что разработчики Firefox исправят такое поведение в новых версиях браузера.
Модальные окна
Когда зрячий пользователь открывает всплывающее окно в интерфейсе, его взгляд падает на то, что внутри этого окна. Однако ему всё ещё доступен контент всего сайта, и при необходимости он может вернуть своё внимание к нему.
Когда работа с сайтом идёт через скринридер, ситуация для пользователя меняется. Если всплывающее окно не сделать модальным, то у него не будет границ. Как следствие, навигация по сайту станет сложнее: человек может выйти из всплывающего окна по ошибке, используя навигацию по элементам.
При работе с модальными окнами есть стандарт, и, если следовать ему, запирать все всплывающие окна в интерфейсе необязательно.
Но в процессе тестирования мы пришли к выводу, что делать всплывающие окна модальными — хорошая практика, упрощающая работу с сайтом при использовании скринридера.
Мы решили сделать все всплывающие окна модальными. При этом мы оставили пользователю возможность настроить альтернативный сценарий работы с такими окнами.
Вы можете настроить такой сценарий, назначив role="dialog", aria-modal="true"
. В связке VoiceOver + Firefox это решение не поддерживается: подробности есть в закрытом issue.
Diplodoc
Заразившись нашей активностью, разработчики платформы для создания технической документации Diplodoc сделали ряд a11y‑доработок в своём продукте:
Проверили все дополнительные элементы синтаксиса YFM (Yandex Flavored Markdown) и переписали так, чтобы они были видны для скринридеров.
Улучшили флоу работы с документацией через клавиатуру: исправили порядок выбора элементов и добавили возможность выбора всем интерактивным элементам.
Добавили корректные подписи и обозначения для элементов интерфейса, что значительно упростит использование сервиса для пользователей со скринридерами.
Поскольку мы используем Diplodoc для отображения своей документации, эти доработки также повысили доступность документации на сайте Yandex Cloud.
Результаты и планы
Мы сделали первые шаги по улучшению доступности интерфейсов Yandex Cloud для всех пользователей. Нам предстоит перенести опыт на другие интерфейсы сервиса и доработать те проблемы, о которых знаем сейчас.
Gravity UI облегчил нашу работу над доступностью, и мы продолжаем дорабатывать открытые issue с тегом a11y. На момент написания статьи у нас 14 открытых и 24 закрытых issue, вы можете посмотреть на них тут.
Ждём ваших PR и комментариев о доступности и работе сервисов, а также примеров использования Gravity UI на ваших сайтах.
Комментарии (3)
iurii_gudkov
29.10.2024 06:41Используем gravity-ui на проекте, но только ui-kit. Набор базовых компонентов вполне приятный в использовании, легко кастомную тему накатить (и в целом повлиять на поведение компонента), удобные гайды для дизайнеров.
Bone
Жду когда будет готов Navigation -> Action Bar (это ведь такое меню прилипшее книзу, как в мобильных приложениях?)
timofeyev_vv Автор
Самим компонентом можно пользоваться вот тут и тут есть его описание. До лэндинга тоже обязательно дотащим.