1.     О чём эта статья

Уже более 10 лет мы сопровождаем Единый официальный сайт органов власти Ханты-Мансийского автономного округа — Югры (admhmao.ru, далее – сайт), в структуру которого входят ещё 36 сайтов органов власти округа. За это время требования к доступности государственных сайтов несколько раз менялись.

В этой статье мы рассказываем, как выполняли требования законодательства о доступности сайта для инвалидов по зрению, хотя между собой называем это просто «доступностью»: читать мелкий серый текст на белом фоне не любит никто. Покажем на реальном госпроекте, какие грабли ждут команду, когда большой legacy-сайт приводят к современным требованиям доступности. Большинство рекомендаций пригодятся при разработке любого сайта, не только государственного.

Актуальные требования к доступности изложены в Постановлении Правительства Российской Федерации от 07.02.2026 № 102 (здесь можно с ними ознакомиться).

Если коротко, вот с какими типовыми проблемами мы столкнулись — по ним можно быстро определить, «что не стоит делать» на подобном сайте:

· горизонтальная прокрутка при масштабировании до 200 %, прежде всего из-за широких таблиц;

· элементы, недоступные или невидимые при навигации с клавиатуры;

· role="tab" на обычных ссылках меню;

· отсутствие программной отметки текущей страницы (aria-current);

· кнопки-иконки без текстовых меток;

· PDF-просмотрщик с интерфейсом на английском языке;

· динамический контент, о появлении которого пользователь скринридера не узнаёт (фреймы, блоки «Посмотреть все»);

· виджет календаря, в котором нельзя выбрать день;

· изображения без атрибута alt;

· слайдеры с автопереключением;

· недостаточная контрастность текста;

· CAPTCHA без аудиоверсии на русском языке.

Дальше — подробно о каждой проблеме и о том, как мы её решали (или почему пока не решили).

2.     Технологии и инструменты

Сайт работает на платформе Bitrix Framework, бэкенд написан на PHP. Фронтенд: HTML5, CSS3, JavaScript (jQuery и Vue.js).

Для проверки доступности мы использовали:

· NVDA — бесплатная программа экранного доступа (скринридер);

· Lighthouse — автоматический аудит в Chrome DevTools;

· Colour Contrast Analyser — проверка цветового контраста;

· axe DevTools — автоматическая проверка доступности страниц.

3.     Как менялись требования

3.1.          Ранние требования: отдельная версия для слабовидящих

Ранние требования (Приказ Министерства связи и массовых коммуникаций Российской Федерации от 30.11.2015 № 483, текст документа) обязывали предусмотреть на сайте отдельную версию для слабовидящих, если основная версия сайта требованиям приказа не удовлетворяла. Основные требования к такой версии:

· нетекстовая информация и нетекстовые материалы должны быть описаны в текстовом виде;

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

· посетитель должен иметь возможность увеличить размер текста до 200 %, увеличить интервал между буквами, изменить шрифт и цветовую схему.

У версии для слабовидящих, существовавшей на сайте, были свои слабые места:

· скудный выбор цветовых схем — всего две, тогда как на образцовых сайтах встречалось до пяти;

· отсутствие встроенной озвучки текста.

Версия для слабовидящих
Версия для слабовидящих

3.2.          Актуальные требования: доступной должна быть основная версия

В 2024 году подход радикально изменился: требования доступности теперь предъявляются к основной версии сайта, а отдельная версия для слабовидящих «отменяется» (Приказ Минцифры России от 07.11.2023 № 953, текст документа, вступил в силу 01.09.2024, утратил силу 01.03.2026 на основании приказа Минцифры России от 09.12.2025 № 1160; многие его требования в более проработанном виде «перекочевали» в актуальное Постановление Правительства Российской Федерации от 07.02.2026 № 102).

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

Нам повезло: незадолго до вступления в силу новых требований начался редизайн сайта, включая изменение структуры разделов, и к моменту вёрстки новых шаблонов мы уже знали о грядущих изменениях — новые требования доступности закладывали сразу в шаблоны, а не «прикручивали» задним числом.

4.     Круглый стол: как мы разбирались в требованиях

Вплотную с новыми требованиями мы познакомились на круглом столе «Доступность интернет-ресурсов для особенных людей» в рамках XV Международного IT-Форума (июнь 2024 года, Ханты-Мансийск, запись мероприятия). Коллеги из других регионов, выступавшие с докладами, в дальнейшем не раз помогали нам расшифровывать нюансы требований при посредничестве кураторов сайта из Департамента информационных технологий и цифрового развития Югры. Они же помогли организовать проверку сайта незрячими тестировщиками (новость об этом).

5.     Никакой горизонтальной прокрутки

Самая большая проблема нашего legacy-сайта была связана с требованием:

«текстовая информация, размещаемая на официальных сайтах, масштабируется не менее чем на 200 процентов исходного масштаба интернет-страницы без применения вспомогательных технологий, потери функциональности и появления горизонтальной полосы прокрутки».

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

На сайте с момента его разработки были разделы со статичными таблицами, например, вкладка «Документы» на главной. При увеличении масштаба до 200 % и на мобильных устройствах они неизбежно давали горизонтальную прокрутку.

Старый вид таблиц: внизу страницы видна горизонтальная полоса прокрутки
Старый вид таблиц: внизу страницы видна горизонтальная полоса прокрутки

Мы переработали таблицы в адаптивные: на широких экранах это обычная таблица, а на узких — строки превращаются в компактные карточки.

Новый вид таблиц (desktop)
Новый вид таблиц (desktop)
Новый вид таблиц (mobile)
Новый вид таблиц (mobile)

6.     Работа только с клавиатуры

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

При тестировании возникали в основном две проблемы:

· на элемент нельзя навести фокус;

· фокус на элемент наводится, но визуально никак не выделяется (нет обводки).

Особое внимание уделяли «важным страницам»: главной и сервисам — телефонному справочнику и обращениям граждан, включая формы отправки обращений.

Самой тяжелой задачей при соблюдении требования стало обеспечение корректного скрытия и раскрытия меню третьего уровня с клавиатуры на главной странице и подсайтах. Первичный вариант решения пришлось откатить: при проверке в NVDA он давал новые ошибки. После долгих размышлений и ряда неудачных вариантов реализации пришлось полностью переработать меню.

Меню третьего уровня
Меню третьего уровня

7.     Что нашли незрячие тестировщики

Сайт мы тестировали скринридером NVDA — он популярен среди людей с нарушениями зрения. Скринридер опирается в том числе на атрибуты элементов: прочитает alt у изображения, title у ссылки, aria-label у кнопки.

В 2026 году к тестированию удалось подключить незрячих тестировщиков — их проверка оказалась особенно ценной и выявила проблемы, которые мы сами не заметили. В работе они использовали скринридер и инструменты автоматической оценки доступности — Lighthouse и axe DevTools. Ниже — часть найденных проблем.

7.1.          Вкладки, которые на самом деле ссылки

Ссылки в выпадающем главном меню были размечены атрибутом role="tab". Такая роль обещает пользователю переключение контента без перезагрузки страницы, но по факту происходил переход на другую страницу.

Выпадающее меню сайта
Выпадающее меню сайта
Код элемента «О Департаменте» до и после исправления

До исправления:

<ul class="nav-list-2-lvl flex-column align-items-start" id="pills-tab" role="tablist">
     <li>
            <a class="hm__menu-button px-0 mb-1 lh-sm" role="tab" href="/about/rukovodstvo/">О Департаменте</a>
     </li>
</ul>

После исправления — убрали role="tablist" у <ul> и role="tab" у всех <a>:

<ul class="nav-list-2-lvl flex-column align-items-start! position-relative">
     <li class="d-flex align-items-start dropend">
            <a class="hm__menu-button px-0 mb-1 lh-sm" href="/about/rukovodstvo/">О Департаменте</a>
     </li>
</ul>

В результате ссылки снова стали ссылками — скринридер объявляет их корректно.

7.2.          Скринридер не знает, на какой вы странице

При навигации по меню пользователи скринридеров не получали информацию о том, какой пункт соответствует открытой сейчас странице: текущий пункт выделялся только CSS-классом is-active, который ассистивным технологиям ничего не сообщает.

Решение — атрибут aria-current="page" на ссылке, ведущей на текущую страницу.

Код до и после исправления

До исправления:

<ul class="nav-list-2-lvl flex-column align-items-start" id="pills-tab" role="tablist">
     <li class="is-active">
            <a class="hm__menu-button px-0 mb-1 lh-sm" role="tab" href="/about/rukovodstvo/">О Департаменте</a>
     </li>
</ul>

После исправления:

<ul class="nav-list-2-lvl flex-column align-items-start! position-relative">
     <li class="d-flex align-items-start dropend">
            <a class="hm__menu-button px-0 mb-1 lh-sm" href="/about/rukovodstvo/" aria-current="page">О Департаменте</a>
     </li>
</ul>

7.3.          Кнопки-иконки без имён

У части функциональной кликабельной графики отсутствовали текстовые метки — скринридеру нечего было объявить.

Кнопка поиска
Кнопка поиска

Решение — дать каждой интерактивной иконке aria-label с понятным описанием действия: «Закрыть поиск», «Найти поручения», «Выбрать район Лангепас».

Код кнопки поиска до и после исправления

До исправления:

<button class="btn text-muted ms-4 search" id="iconSearchMain">
     <img class="pe-2" src="/local/templates/hmao/img/hm_magnifying-glass.svg" alt="Поиск">
 </button>

После исправления (иконка стала инлайновым SVG, имя кнопки задаёт aria-label; декоративный SVG скрыт от ассистивных технологий):

<button class="btn text-muted ms-4 search" id="iconSearchMain" data-bs-toggle="collapse" data-bs-target="#collapseSearch" aria-expanded="false" aria-label="Поиск">
     <svg width="24" height="25" viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false">
            <path d="M11 19.5C15.4183 19.5 19 15.9183 19 11.5C19 7.08172 15.4183 3.5 11 3.5C6.58172 3.5 3 7.08172 3 11.5C3 15.9183 6.58172 19.5 11 19.5Z" stroke="#818181" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
            <path d="M21 21.5L16.65 17.15" stroke="#818181" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
     </svg>
 </button>

Отметим: в исходном варианте у <img> был alt="Поиск", поэтому конкретно эта кнопка имя имела. Проблема касалась графических кнопок без alt и без aria-label — единый подход с aria-label на кнопке надёжнее и не зависит от способа вставки иконки.

7.4.          PDF-просмотрщик говорил по-английски

Кнопки управления встроенного PDF-просмотрщика ViewerJS были подписаны на английском языке — неудобно для людей, не владеющих им, а скринридер и вовсе читал английские метки в русском интерфейсе.

PDF-просмотрщик без локализации
PDF-просмотрщик без локализации

Решение — локализовать метки кнопок: Presentation → «Презентация», Fullscreen → «На весь экран» и так далее.

Код кнопок верхней панели до и после локализации

До:

<div id="titlebarRight">
     <button id="presentation" class="toolbarButton presentation" title="Presentation"></button>
     <button id="fullscreen" class="toolbarButton fullscreen" title="Fullscreen"></button>
     <button id="download" class="toolbarButton download" title="Download"></button>
 </div>

После:

<div id="titlebarRight">
     <button id="presentation" class="toolbarButton presentation" title="Презентация"></button>
     <button id="fullscreen" class="toolbarButton fullscreen" title="Полноэкранный"></button>
     <button id="download" class="toolbarButton download" title="Скачать">
            <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-download" viewBox="0 0 16 16" aria-hidden="true" focusable="false">
                   <path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5"></path>
                   <path d="M7.646 11.854a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8.5 10.293V1.5a.5.5 0 0 0-1 0v8.793L5.354 8.146a.5.5 0 1 0-.708.708z"></path>
            </svg>
     </button>
 </div>

Часть кнопок мы скрыли через CSS, чтобы разгрузить интерфейс просмотрщика:

.toolbarButton.fullscreen, .toolbarButton.presentation {
     display: none;
 }
Локализованный PDF-просмотрщик
Локализованный PDF-просмотрщик

7.5.          Фокус не переносится на фреймы обратной связи

В блоке «Мой выбор, моё решение» при нажатии кнопок «Участвовать» и «Сообщить о проблеме» внизу страницы появляется фрейм с контентом, но пользователю скринридера об этом никто не сообщает.

Блок «Мой выбор, моё решение»: фрейм появляется внизу страницы без переноса фокуса
Блок «Мой выбор, моё решение»: фрейм появляется внизу страницы без переноса фокуса

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

7.6.          «Посмотреть все» раскрывалось не туда

В блоке полезных ресурсов при нажатии кнопки «Посмотреть все» дополнительные ссылки появлялись в DOM выше кнопки. Пользователь, двигающийся по странице последовательно, просто не обнаруживал новый контент — он оставался «за спиной».

До исправления: новые ссылки раскрываются над кнопкой «Посмотреть все»
До исправления: новые ссылки раскрываются над кнопкой «Посмотреть все»

Незрячие тестировщики предложили компромиссный вариант: дописать в aria-label кнопки подсказку вида «раскрыть все ссылки (после активации кнопки нажимайте стрелку вверх)». Мы решили проблему иначе — исправили сам порядок элементов:

· блок со скрытыми ссылками перемещён ниже кнопки «Посмотреть все»;

· добавлена вторая кнопка «Скрыть все» — она размещена после блока ссылок и появляется при раскрытии;

· кнопка «Посмотреть все» при нажатии скрывается, но пользователь продолжает перемещаться по элементам с помощью клавиши Tab, фокус естественным образом перескакивает на первый элемент в раскрывшемся списке.  

Так последовательность движения по странице сохраняется: раскрыл — двигаешься дальше вниз по новым ссылкам.

После исправления: ссылки раскрываются под кнопкой, порядок движения по странице сохранён
После исправления: ссылки раскрываются под кнопкой, порядок движения по странице сохранён
Код блока до и после исправления

До исправления:

<div class="tab-pane fade show active" id="pills-population" role="tabpanel" aria-labelledby="pills-population-tab">
     <div class="row gy-3! service-container">
            <!-- Основной блок со ссылками -->
     </div>
     <div id="showMore-population" class="row collapse">
            <!-- Скрытый блок со ссылками: находится ВЫШЕ кнопки -->
     </div>
     <button class="accordion-button hm__collapse-button color-links hm__accordion-button! collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#showMore-population" aria-expanded="false" aria-controls="showMore-population" aria-label="Посмотреть все ссылки раздела «Гражданам»">
            Посмотреть все
     </button>
 </div>

После исправления:

<div class="tab-pane fade show active" id="pills-population" role="tabpanel" aria-labelledby="pills-population-tab">
     <div class="row gy-3! service-container">
            <!-- Основной блок со ссылками -->
     </div>
     <button class="startService accordion-button collapsed hm__collapse-button color-links hm__accordion-button!" type="button" data-bs-toggle="collapse" data-bs-target="#showMore-population" aria-expanded="false" aria-controls="showMore-population" aria-label="Посмотреть все ссылки раздела «Гражданам»">
            Посмотреть все
     </button>
     <div id="showMore-population" class="row collapse">
            <!-- Скрытый блок со ссылками: теперь НИЖЕ кнопки -->
     </div>
     <button class="endService accordion-button collapsed hm__collapse-button color-links hm__accordion-button!" style="display:none" type="button" data-bs-toggle="collapse" data-bs-target="#showMore-population" aria-expanded="false" aria-controls="showMore-population" aria-label="Скрыть все ссылки раздела «Гражданам»">
            Скрыть все
     </button>
 </div>

Обратите внимание на aria-label: его текст должен начинаться с видимого текста кнопки («Посмотреть все…»), иначе пользователи голосового управления не смогут активировать кнопку по её видимой надписи (критерий WCAG 2.5.3 Label in Name).

7.7.          Календарь, в котором нельзя выбрать день

В виджете календаря пользователь скринридера мог перемещаться по годам и месяцам, но не мог выбрать конкретный день: NVDA не прочитывал числа месяца.

Недоступный виджет календаря
Недоступный виджет календаря
Код виджета (фрагмент)
<div class="ui-day-picker-month">
     <div class="ui-day-picker-week --week-days">
            <div class="ui-day-picker-week-day">Пн</div>
            <!-- Остальные дни недели -->
     </div>
     <div class="ui-day-picker-week">
            <!-- Первая неделя месяца -->
            <button type="button" class="ui-day-picker-day" data-day="1" data-month="5" data-year="2026" data-tab-priority="true" role="gridcell" tabindex="0">
                   <span class="ui-day-picker-day-inner">1</span>
                   <span class="ui-day-picker-day-marks"></span>
            </button>
            <!-- Остальные числа недели -->
     </div>
     <!-- Остальные недели месяца -->
 </div>

Это встроенный виджет календаря «Битрикса», поэтому вместо переписывания его на классическую доступную разметку (таблица <table> с навигацией по ячейкам) мы пошли по пути прагматичного упрощения: скрыли виджет от скринридера атрибутом aria-hidden="true" и оставили возможность ввести дату вручную с клавиатуры. Для поля добавили скрытую подпись — теперь при фокусе на поле скринридер сообщает: «введите дату поручения от, редактор, дд точка мм точка гггг».

Код интерфейса выбора даты после исправления
<div id="datePicker_DATE_FROM" class="hm__datePicker position-relative">
     <span class="hm__wrap-calendar input-daterange pb-1 pb-sm-0 btn-group!">
            <label class="visually-hidden" for="date_from">
                   Введите дату поручения от
            </label>
            <input type="text" id="date_from" class="hm__calendar form-control" name="date_from" value="">
            <div id="DATE_FROM_BUTTON" class="hm__calendar-icon" aria-hidden="true">
            </div>
     </span>
 </div>

Важный нюанс: aria-hidden="true" нельзя вешать на элемент, который остаётся доступным с клавиатуры, — скринридер «спотыкается» о фокусируемый, но скрытый элемент. Если кнопка открытия календаря фокусируема, ей дополнительно нужен tabindex="-1". В нашем случае кнопка календаря исключена из порядка табуляции, не доступна с клавиатуры.

7.8.          Подписи к изображениям

Одно из ключевых требований доступности: вся нетекстовая информация должна быть представлена и в текстовом виде. Для изображений это означает обязательное заполнение атрибута alt — он должен описывать, что изображено.

Работа тут делится на двоих: разработчики дают контент-менеджерам возможность заполнить alt (и по возможности проверяют результат), а контент-менеджеры заполняют описания.

7.8.1.   Автоматическая проверка

Раз в год мы запускаем по сайтам PHP-скрипт. Он выводит отчёт о страницах, где есть изображения без атрибута alt. Проверяем в основном новости и разделы, где департаменты создают страницы, — за последний год, т.к. страницы прошлых лет уже проверены в предыдущие итерации. По результатам формируем файл для каждого из 37 сайтов, и ответственные за сайты вручную добавляют недостающие описания.

PHP-скрипт проверки атрибута alt
// Ищем теги <img>, внутри которых нет атрибута alt
 $pattern_without_attr = "/<img((?!alt=)[^<>])+>/im";
 
 // Типы инфоблоков, которые проверяем (здесь — новости)
 $arIblockTypes = Array(
     "news" => "news",
 );
 
 // Проверяемые сайты: адрес и код сайта в Битриксе
 $arSites = array(
     "dh" => array(
            "PROPERTY_WWW_VALUE"     => "https://depsport.admhmao.ru",
            "PROPERTY_SITE_ID_VALUE" => "dh",
     ),
 );
 
 foreach ($arIblockTypes as $iblock_type) {
     foreach ($arSites as $site) {
            print_r($site["PROPERTY_WWW_VALUE"] . "\n");
            print_r($site["PROPERTY_SITE_ID_VALUE"] . "\n");
            print_wps_urls($site, $iblock_type);
     }
 }
 
 function print_wps_urls($site, $iblock_type) {
     CModule::IncludeModule("iblock");
     $pattern = $GLOBALS["pattern_without_attr"];
     $arSelect = Array("ID", "NAME", "DATE_CREATE", "DETAIL_PAGE_URL", "DETAIL_TEXT");
     // Берём активные элементы, созданные с начала года.
     // DETAIL_TEXT — поле «Детальное описание», в нём хранится текст новости
     $arFilter = Array(
            "IBLOCK_SITE_ID" => $site["PROPERTY_SITE_ID_VALUE"],
            ">DATE_CREATE"   => "01.01.2026",
            "ACTIVE_DATE"    => "Y",
            "ACTIVE"         => "Y",
            "IBLOCK_TYPE"    => $iblock_type,
            "DETAIL_TEXT"    => "%%",
     );
     $res = CIBlockElement::GetList(Array(), $arFilter, false, false, $arSelect);
     while ($ob = $res->GetNextElement()) {
            $arFields_2 = $ob->GetFields();
            preg_match_all($pattern, $arFields_2["DETAIL_TEXT"], $matches, PREG_SET_ORDER);
            if (empty($matches)) {
                   continue;
            }
            // Выводим адрес и название проблемной страницы и сами теги без alt
            print_r("\nСсылка на элемент:\n");
            print_r($site["PROPERTY_WWW_VALUE"] . $arFields_2['DETAIL_PAGE_URL'] . "\n");
            print_r("Название элемента:\n");
            print_r($arFields_2["NAME"] . "\n");
            foreach ($matches as $match) {
                   print_r($match[0] . "\n");
            }
     }
 }

Запускаем скрипт в модуле «Командная PHP-строка» «Битрикса».

Командная PHP-строка
Командная PHP-строка

Пример вывода:

https://depsport.admhmao.ru
 dh
 
 Ссылка на элемент:
 https://depsport.admhmao.ru/news/6470065/
 Название элемента:
 25 ноября в Ханты-Мансийске стартует первый этап Кубка России по биатлону
 <img width="980" src="https://www.csp-ugra.ru/upload/poslednie_s_06.09.12/DSC_020202027.jpg" height="651">
 <img width="980" src="https://www.csp-ugra.ru/upload/poslednie_s_06.09.12/DSC_00%D1%861270.jpg" height="651">
 <img width="980" src="https://www.csp-ugra.ru/upload/poslednie_s_06.09.12/DSC_0056.jpg" height="651">

7.8.2.   Помощь контент-менеджерам

Чтобы alt заполнялся уже на этапе создания новости, в админке рядом с каждым загружаемым изображением есть текстовое поле для описания — оно и выводится в атрибуте alt. Если контент-менеджер описание не заполнил или подвязал к новости фотогалерею, система подставляет подпись по шаблону — номер изображения и название новости:

<img class="hm__image f-lazyload is-lazyloaded" alt="Фото 1 к новости «Северный мох в Китае: югорчане с успехом представляют свои товары на выставке»" src="/upload/resize_cache/iblock/8f3/.../a3ad3912_3e57_4c71_8644_8e7181cca328.jpg">
Описание к изображению в админке
Описание к изображению в админке

7.8.3.   Известные ограничения проверки

Честно перечислим, что наш скрипт пока не умеет:

· Проверка качества описания. alt может быть заполнен бесполезно: «DCS00567.jpg» или просто «изображение». Скрипт такие случаи не ловит — в планах добавить проверку на подобные шаблоны, пока это остаётся на совести администраторов сайтов. Вручную мы проверяем в основном только главные страницы.

· Автоподписи по шаблону. Подпись вида «Фото 1 к новости …» формально заполняет alt, но содержание изображения не описывает — такие изображения скрипт тоже не отслеживает. В планах на будущее реализовать автоматическую генерацию описаний изображений через ИИ.

7.9.          Цель ссылки

Требование:

«цель каждой ссылки, размещаемой на официальных сайтах, определяется из текста ссылки или из текста ссылки вместе с её контекстом, который может быть распознан программным обеспечением».

Раз в год мы проверяем ссылки в текстах страниц на наличие атрибута title — тем же скриптом, что и изображения, только с другим регулярным выражением.

При этом мы понимаем ограничения такого подхода. В современной практике доступности:

· title считается слабым решением — скринридеры читают его непоследовательно, а на сенсорных устройствах он недоступен вовсе;

· предпочтительнее осмысленный текст самой ссылки или aria-label;

· наличие title само по себе не гарантирует, что цель ссылки понятна: формально требование — про текст ссылки и её контекст, а не про конкретный атрибут.

7.10.   Осторожнее со слайдерами

В старой версии сайта на главной странице был слайдер новостей с автопереключением. По современным требованиям доступности движущийся и автоматически обновляемый контент должен иметь средства управления — возможность приостановить или отключить автопереключение.

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

7.11.    Контрастность

Требование доступности: коэффициент контрастности текста по отношению к фону — не менее 4,5:1.

В текущем году после повторной проверки мы внесли небольшие правки в цвета шрифтов. Проверяли онлайн-инструментом Adobe Color Contrast Analyzer (подобных сервисов много). Нашли несоответствие в контрастности даты новости на главной странице сайта — там фон блока темнее, чем на сайтах исполнительных органов власти. После правки контрастность этого элемента составила 4,51:1.

Контрастность даты новости тоже важна
Контрастность даты новости тоже важна

7.12.          Русская CAPTCHA

Если на сайте есть CAPTCHA, по требованиям доступности у неё должна быть аудиоверсия: пользователь может не вводить текст с изображения, а прослушать запись, где сквозь шум диктуются цифры. Второе требование — CAPTCHA должна быть на русском языке, и это касается как текстовой, так и аудиоверсии.

Мы несколько лет используем Yandex SmartCaptcha — в ней есть опция аудиокапчи на русском языке, что закрывает оба требования.

8.     Итоги

8.1.          Сводная таблица изменений

Проблема

Как было

Как исправили

Работа с клавиатуры

Невозможно навести фокус на элемент

Focus management, стилизация фокуса

Таблицы

Горизонтальный скролл

Адаптивные карточки

Меню

role="tab" на ссылках

Семантическая навигация

Текущая страница

Только class="is-active"

aria-current="page"

Слайдер

Автопереключение

Убрали полностью

Кнопки-иконки

Нет текстовых меток

aria-label

PDF-просмотрщик

Кнопки на английском

Локализован на русский

Фреймы обратной связи

Фокус не переносится на фрейм

Не решено

Блок «Посмотреть все»

Новые элементы появлялись над кнопкой

Блок перемещён ниже кнопки, порядок движения по странице сохранён

Виджет календаря

Недоступен для скринридера

Виджет скрыт (aria-hidden="true"), оставлен ручной ввод даты с подписью поля

Контрастность

Коэффициент ниже 4,5:1

Минимальный коэффициент — 4,51:1

8.2.          Что нашли автоматические проверки, а что — люди

Почти всё тестирование доступности мы делали вручную, включая проверки незрячими тестировщиками. Автоматизировали только две вещи: поиск изображений без описаний и ссылок без заданного контекста на внутренних страницах.

8.3.          Рекомендации другим командам

· Обращайтесь к людям с опытом адаптации сайтов. Самостоятельно понять все нюансы требований сложно, а цена неверной трактовки — переделки.

· Закладывайте доступность с самого начала. Разрабатывайте дизайн-макеты с учётом требований, чтобы потом не переделывать вёрстку.

· Помните: доступность — задача не только разработчиков. Контент-менеджеры, наполняющие сайт, влияют на неё не меньше.

· Закладывайте время. Доступность — это системная работа, особенно на больших legacy-проектах.

· Привлекайте к тестированию пользователей с нарушениями зрения. Никто не расскажет о проблемах доступности сайта лучше, чем люди, которые пользуются им через скринридер каждый день.