~/wiki / dostupnost / klaviaturnaya-navigatsiya-v-ui

Пройди свой сайт без мышки. Скорее всего ты застрянешь на третьем экране

Основной чат

Чат для вайбкодеров: новости, гайды, поиск исполнителей, маркетплейс и разбор реальных кейсов.

$ cd раздел/ $ join vibe dev
Пройди свой сайт без мышки. Скорее всего ты застрянешь на третьем экране - обложка

Закройте глаза, отодвиньте мышку, уберите руку с трекпада. Откройте свой продукт и пройдите главный сценарий — регистрацию, оформление заказа, создание проекта — только с клавиатуры. Не как упражнение на доступность, а как обычный пользователь, который сегодня сломал мышь, или едет в поезде с тачпадом, который глючит, или просто привык работать с клавиатуры.

Скорее всего, вы застрянете. Иногда на втором экране, чаще на третьем. На модалке, которую не закрыть по Escape. На дропдауне, который не открывается с Enter. На кастомном чекбоксе, который вообще не получает фокус. На «крестике», который визуально есть, а в tab-порядке его нет.

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

Почему это вообще проблема продукта, а не «инклюзивности»

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

Но клавиатурная навигация — это не подмножество доступности. Это базовая механика интерфейса, которой пользуются:

  • любой опытный пользователь, который быстрее печатает, чем целится курсором
  • все, кто работает на ноутбуке без мыши в дороге
  • пользователи скринридеров и людей с моторными ограничениями
  • автотесты, которые ходят по интерфейсу через tab/enter
  • браузерные расширения и автозаполнение, которые опираются на фокус
  • LLM-агенты и autofill из паролей, которым нужен предсказуемый focus order

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

Где это бьёт по метрикам

В команде это редко всплывает как «проблема доступности». Всплывает иначе:

  • падает конверсия в формах с нестандартными полями (даты, мультиселекты, загрузка файлов)
  • растёт количество тикетов «не могу закрыть окно», «ничего не нажимается»
  • автотесты на E2E постоянно падают, и QA пишет хрупкие селекторы вместо опоры на роли
  • новый онбординг с модалками показывает странный drop-off у опытных пользователей — а это те, кто жмёт Esc по привычке и улетает не туда

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

Тест на 10 минут: пройдите свой продукт

Прежде чем читать дальше, сделайте упражнение. Оно занимает меньше времени, чем дочитать эту статью, и даёт больше, чем любой аудит.

Правила

  1. Откройте продукт в обычном браузере. Не в режиме разработчика, не с включённым скринридером — просто браузер.
  2. Уберите руку с мыши. Совсем. Положите на колено.
  3. Пройдите ключевой сценарий целиком: от входа до получения результата.
  4. Разрешены только: Tab, Shift+Tab, Enter, Space, Escape, стрелки.

Что фиксировать

Заведите простой документ и пишите по ходу:

  • Где пропал фокус. Нажал Tab — и не видно, на чём ты. Это самая частая поломка.
  • Где фокус ушёл не туда. Открыл модалку — а фокус остался на кнопке под ней.
  • Где порядок странный. Tab прыгает с шапки в футер, потом в середину.
  • Что не нажимается. Кастомный селект, свитч, карточка-кнопка.
  • Что не закрывается. Модалка, попап, дропдаун без Escape.
  • Куда нельзя добраться вообще. Иконка-меню, тултип, действие в таблице.

После такого прохода обычно набирается 15–30 пунктов на средний продукт. Это и есть ваш бэклог на ближайший спринт по клавиатуре — и он чинится быстрее, чем кажется.

Анти-паттерны, которые встречаются почти везде

Прежде чем разбирать, как строить хорошо, полезно увидеть типовые поломки. Если узнали что-то своё — это нормально, они есть у всех.

Невидимый фокус

Самый частый случай: кто-то в CSS написал outline: none на кнопках, потому что «синяя рамка некрасивая», и не добавил замену. Tab работает, но пользователь не видит, где он. Это эквивалент курсора мыши, который стал прозрачным.

Focus trap, которого нет

Открылась модалка — а Tab продолжает гулять по странице под ней. Пользователь жмёт Enter и случайно нажимает кнопку, которую даже не видит. Обратная история — focus trap есть, но из модалки не выйти, потому что Escape не повешен.

Кастомные компоненты без ролей

Div с обработчиком onClick — это не кнопка. Для глаза — да, для клавиатуры и скринридера — просто прямоугольник. Если в дизайн-системе есть <Card clickable>, который под капотом div — это бомба замедленного действия.

Tab-порядок по DOM, а не по логике

Визуально кнопка «Продолжить» внизу формы. В DOM она оказалась раньше полей, потому что так удобнее верстальщику. Пользователь жмёт Tab и улетает на «Продолжить» до того, как заполнит форму.

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

Если из этого списка узнали хотя бы три пункта — добро пожаловать в клуб. Дальше разберём, как это чинить системно, а не точечно.

Как дизайнеру встроить клавиатурный сценарий в работу

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

Что закладывать в макет, а не оставлять на потом

В Figma редко рисуют focus-состояния. Это первый признак того, что про клавиатуру забыли. Минимум, который должен быть в любом интерактивном компоненте:

  • default, hover, focus-visible, active, disabled
  • состояние для focus внутри группы (например, выбранный чип в фокусе)
  • как выглядит focus на тёмном фоне и на цветной кнопке — контраст не должен ломаться

Focus — это не hover. Hover показывает «мышь сверху», focus показывает «сейчас я тут, нажму Enter — что-то произойдёт». Если они выглядят одинаково, пользователь с клавиатуры теряется в момент, когда курсор мыши тоже над страницей.

Порядок чтения как часть макета

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

Особенно это важно для:

  • модалок с формой и кнопками «Отмена / Сохранить»
  • таблиц с действиями в строках
  • сайдбаров с вложенной навигацией
  • многошаговых форм, где «Назад» и «Далее» визуально разнесены

Шорткаты: договоритесь о словаре

Если в продукте появляются хоткеи (/ для поиска, ? для подсказок, g + i для перехода), они должны быть едиными во всех разделах. Самая частая ошибка — каждая команда придумывает свои, и в одном разделе Esc закрывает модалку, а в другом сбрасывает фильтры.

Полезно завести страницу в дизайн-системе: список глобальных шорткатов, список зарезервированных клавиш (Esc, Enter, Space, Tab — их нельзя переопределять под свои хотелки) и правило, как показывать подсказку с шорткатом в тултипе.

Диагностика на ревью макета

Когда вы смотрите чужой макет (или свой через день), есть короткий список вопросов, который вылавливает большинство клавиатурных проблем ещё до разработки.

Вопросы для дизайн-ревью

  • Где здесь фокус по умолчанию, когда экран открылся?
  • Что произойдёт, если нажать Tab прямо сейчас? Куда уйдёт фокус?
  • Как пользователь закроет это окно без мыши?
  • Есть ли состояние focus-visible у всех кликабельных элементов, включая иконки и карточки?
  • Если это модалка — куда вернётся фокус после её закрытия?
  • Если это список — стрелки внутри работают? Tab выходит наружу?
  • Disabled-кнопка получает фокус или пропускается? Что лучше для этого сценария?

Последний пункт стоит пояснить. Полностью убирать disabled-элемент из tab-порядка — значит, пользователь не поймёт, почему «Сохранить» недоступно. Часто правильнее оставить фокус, но дать понятную причину рядом.

Сценарии, на которых обычно ломается

Соберите внутренний список «адских флоу» и прогоняйте их с клавиатуры на каждом релизе:

  • логин с переключением на код из СМС
  • оплата с выбором карты и вводом CVC
  • загрузка файла перетаскиванием (и альтернатива через кнопку)
  • поиск с автодополнением: стрелки выбирают подсказку, Enter подтверждает, Esc закрывает
  • мультиселект с поиском внутри
  • таблица с inline-редактированием ячеек

Это места, где обычно стоят кастомные компоненты, и где клавиатурный сценарий ломается первым.

Типичные ошибки на стороне дизайнера

Не все клавиатурные баги — вина разработки. Часть из них растёт прямо из макета.

Прозрачный focus ради эстетики

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

Иконка без текста и без роли

Иконка-кнопка «три точки» в углу карточки. На макете красиво, в проде — для скринридера это «button», без названия. Дизайнер должен в макете подписывать, какой aria-label у такой кнопки. Это не работа разработчика — он не знает, что эта иконка означает «Открыть меню действий с задачей».

Модалка, в которой нет первого фокуса

Открылась — а фокус непонятно где. Хорошая модалка ловит фокус на первом значимом элементе: на поле ввода, если есть форма; на заголовке, если это подтверждение; на главной кнопке, если это короткий вопрос «Удалить?». В макете это решение принимается, а не оставляется «на разработку».

Тултип, который виден только на hover

Подсказка по иконке появляется на наведение мыши. С клавиатуры — никак. Если тултип несёт важную информацию (например, объясняет, что значит иконка), он должен показываться и на focus.

Короткий итог

Клавиатурная доступность — это не «специальная фича для слепых». Это базовая характеристика интерфейса, как контраст или читаемость. Она ловится за один проход без мыши, чинится в основном на уровне макета и дизайн-системы, и окупается ростом скорости работы внутренних пользователей раньше, чем формальной галочкой по WCAG. Дальше разберём, как превратить это в постоянную практику команды, а не разовый героизм одного дизайнера.

Когда в команде сидит AI и пишет код

Сейчас половина продуктовых команд так или иначе использует AI-ассистента: Copilot, Cursor, что-то на основе MCP, генерацию компонентов из Figma. Это сильно меняет, кто и где роняет клавиатурную доступность. Раньше виноват был дизайнер, который не подумал, или разработчик, который не успел. Теперь добавился третий: модель, которая бодро сгенерировала <div onClick={...}> вместо кнопки, потому что в обучающей выборке такого было много.

Что AI делает плохо по умолчанию

Без явных инструкций модель почти всегда:

  • ставит интерактивность на div и span, потому что это короче
  • забывает aria-label на иконочных кнопках
  • придумывает кастомный dropdown без управления стрелками
  • делает модалку без trap фокуса и без возврата фокуса на триггер
  • использует tabindex="-1" и tabindex="3" так, как будто это нормально

Это не злой умысел, это статистика. Если в промпте не сказано «семантические теги, focus management, клавиатурная навигация», модель оптимизирует на «выглядит как на скриншоте».

Что должно быть в системном промпте команды

Если у вас есть общий промпт для AI-агента (в Cursor, в кастомном тулинге, в MCP-сервере для Figma), туда стоит зашить три вещи:

  • использовать нативные элементы: button, a, input, dialog, и только при необходимости — кастом с role
  • любой интерактивный элемент должен быть достижим Tab и активироваться Enter/Space
  • при генерации модалок, меню и комбобоксов опираться на ARIA Authoring Practices, а не выдумывать

Это короткие правила, но они меняют выдачу заметно. Проверяется просто: попросите сгенерировать «дропдаун с поиском» до и после — разница видна сразу.

Figma → код через MCP: где теряется доступность

Когда дизайн уезжает в генерацию через MCP или плагин, теряется именно семантика. В макете «карточка с тремя точками» — это фрейм, прямоугольник и иконка. Модель не знает, что три точки — это кнопка «Открыть меню действий». Поэтому в макете нужно явно подписывать роли и aria-label прямо в слое или в описании компонента. Это не лишняя работа — это единственный способ донести смысл до автогенерации.

Как проверять клавиатурное качество

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

Минута на компонент

Перед мерджом компонента в дизайн-систему:

  • Tab по нему — фокус виден, порядок логичный
  • Enter и Space делают то, что ожидается от его роли
  • Esc закрывает, если есть что закрывать
  • стрелки работают, если это список, меню или таблица
  • focus-visible не пропадает на тёмной теме и на цветном фоне

Автоматика как нижний порог

Axe, Lighthouse, Storybook a11y addon ловят грубые вещи: отсутствие label, кнопку без имени, контраст фокуса ниже порога. Это не заменяет ручную проверку, но не пускает откровенный мусор в main. Полезно прикрутить такие проверки в CI на уровне сторибука компонентов.

Регулярный прогон сценариев

Раз в спринт или раз в релиз — кто-то один проходит ключевые флоу полностью без мыши. Не для галочки, а с записью экрана и списком мест, где застрял. Это та же практика, что и кросс-браузерное тестирование: скучно, но единственное, что реально работает.

Как объяснить это команде и не быть занудой

«Нам нужна accessibility» — фраза, которая отскакивает от любого бэклога. Работает другой заход.

Говорите про скорость, а не про инвалидность

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

Покажите видео, а не отчёт

Запишите тридцать секунд, как вы пытаетесь оформить заказ без мыши и застреваете на третьем экране. Это сильнее любого WCAG-аудита. После такого видео разговор про приоритет задачи занимает пять минут вместо двух недель.

Встройте в definition of done

Не отдельной задачей «починить доступность через квартал», а строкой в DoD компонента: «проходится с клавиатуры, focus-visible на всех состояниях, корректные роли». Тогда это не героизм, а норма, и не нужно каждый раз воевать за приоритет.

Вопросы, которые стоит задавать на синке

  • какой клавиатурный сценарий у этой фичи?
  • что делает Esc на этом экране?
  • куда возвращается фокус после закрытия модалки?
  • если это новый кастомный компонент — на какой паттерн из ARIA он опирается?

Эти четыре вопроса на планировании экономят недели переделок.

Короткий итог

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

Чеклист перед релизом фичи

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

Клавиатура

  • по всему флоу можно пройти от первого экрана до подтверждения, не трогая мышь
  • порядок Tab идёт сверху вниз, слева направо, без скачков через всю страницу
  • focus-visible виден на светлой и тёмной теме, на цветных кнопках и поверх картинок
  • ловушки фокуса есть только в модалках и дровэрах, и они отпускают по Esc
  • после закрытия модалки фокус возвращается на элемент, который её открыл
  • skip link «к основному контенту» работает и не спрятан намертво

Семантика и роли

  • кастомные компоненты опираются на конкретный паттерн ARIA, а не на «ну, похоже на дропдаун»
  • иконки-кнопки имеют aria-label или скрытый текст
  • состояния (выбрано, развёрнуто, загружается) переданы через aria-*, а не только цветом
  • ошибки формы связаны с полями через aria-describedby, а не висят рядом в воздухе

AI-генерация

  • в системный промпт зашиты правила про семантику, фокус и ARIA
  • сгенерированный компонент прогнан через axe или storybook a11y до ревью
  • ревьюер прошёл компонент клавиатурой руками, а не поверил скриншоту

Анти-паттерны, которые встречаются чаще всего

Это не экзотика, а вещи, которые попадают в прод почти в каждом проекте, где доступность не зашита в процесс.

Div, который притворяется кнопкой

Самый частый случай — особенно после AI-генерации. Визуально кнопка, кликается мышью, с клавиатуры не активируется, скринридер видит «группа». Чинится переписыванием на button или добавлением role, tabindex и обработчика Enter/Space. Лучше первым способом.

Модалка без возврата фокуса

Открыли диалог, закрыли — фокус улетел в начало страницы или в body. Клавиатурный пользователь теряет место и начинает заново. Возврат фокуса на триггер должен быть дефолтом компонента модалки, а не задачей каждого, кто её использует.

Outline: none без замены

Классика, которая до сих пор приезжает из «красивых» темплейтов. Дизайнеру не нравится системный контур — он его убирает и забывает нарисовать свой. В результате клавиатурный пользователь не понимает, где он находится. Если убираете outline, обязательно рисуете focus-visible поверх.

Тосты и автодисмисс важных сообщений

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

Кастомный селект ради дизайна

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

Вопросы для ревью дизайна и кода

Эти вопросы стоит задавать одинаково — и на ревью макета в Figma, и на пул-реквесте.

По макету

  • где здесь фокус, и как он выглядит на каждом состоянии?
  • какие элементы — кнопки, какие — ссылки, какие — переключатели? подписано ли это в слоях?
  • что происходит по Esc на этом экране?
  • куда уезжает фокус после успешной отправки формы?

По коду

  • если это не нативный элемент — на какой ARIA-паттерн опирается реализация?
  • что в DOM-порядке: совпадает ли он с визуальным?
  • какие aria-* атрибуты меняются в рантайме, и кто их обновляет?
  • что говорит скринридер при ошибке валидации?

По AI-сгенерированному коду

  • какие правила доступности были в промпте?
  • прошёл ли результат автоматическую проверку до ревью человеком?
  • кто-то реально нажимал Tab по этому компоненту, или мы смотрим только на скриншот?

Что забрать с собой

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

$ cd ../ ← назад к Доступность