Перейти к содержанию

Клавиатурный маршрут по учебной странице

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

Продолжим файл a11y-lab/lesson.html из предыдущего урока. Первый результат — возможность перейти к статье, пропустив повторяемый хедер. Второй — понятная последовательность ссылок без вручную назначенных положительных значений tabindex. Эти части решают одну задачу: читатель должен выбирать нужное действие, не обходя лишние элементы на каждой главе.

Переход к основному содержимому

Представим длинный хедер с разделами библиотеки. На первой странице читателю полезно познакомиться с ним, но в следующей главе он хочет продолжить чтение. Для этого добавим в начало body, перед header, обычную ссылку. У существующего main изменим только открывающий тег:

<a class="skip-link" href="#main-content">Перейти к содержимому</a>

<!-- Здесь остаётся существующий header. -->

<main id="main-content" tabindex="-1">
  <!-- Здесь остаются хлебные крошки и статья урока 2. -->
</main>

href связывает действие с действительной целью. tabindex="-1" позволяет программно сфокусировать main, но не добавляет его в обычную последовательность Tab. Мы не превращаем содержимое в кнопку и не назначаем ему роль управления. Цель фокуса нужна только для перехода через повторяемую область.

Создайте lesson.css со следующим начальным содержимым. В этом уроке хедер остаётся в обычном потоке; закрепление рассмотрим отдельно, когда разберём перекрытие фокуса.

* { box-sizing: border-box; }

body {
  margin: 0;
  color: #20212b;
  background: #fff;
  font-family: system-ui, sans-serif;
  line-height: 1.6;
}

.site-header, main, .site-footer {
  max-inline-size: 75rem;
  margin-inline: auto;
  padding: 1rem;
}

article { max-inline-size: 48rem; }

.skip-link {
  position: absolute;
  inset-block-start: 0.5rem;
  inset-inline-start: 0.5rem;
  z-index: 100;
  padding: 0.75rem 1rem;
  color: #fff;
  background: #332057;
  transform: translateY(-200%);
}

.skip-link:focus { transform: none; }

a:focus, button:focus, input:focus, textarea:focus {
  outline: 3px solid #56318c;
  outline-offset: 3px;
}

Ссылка находится вне видимой части до получения фокуса и появляется после перехода к ней. Она не скрывается через display: none или атрибут hidden, которые убрали бы её из обычного управления. Здесь скрытие относится к оформлению одного специального элемента. Не переносите этот приём на заголовок статьи или на сообщения о результате: их назначение отличается.

Такой механизм связан с требованием обхода повторяемых блоков, которое объясняется в WAI, Bypass Blocks. Ссылка — один из способов решить задачу; наличие структурных областей также полезно для других способов навигации. Мы сохраняем оба механизма, потому что они обслуживают разные действия читателя.

Положение после активации

Одной прокрутки недостаточно для понятного клавиатурного маршрута. Если экран покажет статью, но последующий Tab вернёт человека в середину хедера, визуальное положение и место управления разойдутся. Поведение якорного перехода следует оценивать в поддерживаемых браузерах. В учебном стенде зададим целевое положение явно, сохранив стандартное изменение фрагмента URL.

Добавьте в head после ссылки на CSS подключение lesson.js с defer. Создайте этот файл со следующим полным начальным содержимым:

<script src="lesson.js" defer></script>
const skipLink = document.querySelector('.skip-link');
const main = document.getElementById('main-content');

skipLink.addEventListener('click', () => {
  requestAnimationFrame(() => {
    main.focus({ preventScroll: true });
  });
});

В обработчике нет preventDefault: браузер продолжает обычный переход к фрагменту. Запланированный вызов переносит фокус на main, не требуя дополнительной прокрутки от focus. Параметр preventScroll относится именно к этому вызову, а не запрещает стандартный переход ссылки; его действие описано в справочнике MDN для focus. Если JavaScript недоступен, ссылка всё равно содержит действительный якорь; поведение фокуса тогда определяется браузером.

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

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

Естественный порядок элементов

В нашем HTML сначала находится специальная ссылка, затем бренд и основное меню, после них хлебные крошки и оглавление. Этот порядок должен оставаться осмысленным независимо от CSS. Если визуально переместить оглавление в боковую колонку, его место в документе не исчезнет. Читатель без визуальной карты продолжит встречать его там, где оно записано.

Положительные значения tabindex задают отдельную последовательность и заставляют поддерживать её при каждом добавлении элемента. Например, новая ссылка с tabindex="1" может внезапно предшествовать всему хедеру. Устойчивее исправить порядок HTML и выбрать подходящую компоновку. Связь осмысленного порядка с чтением и управлением рассматривается в WAI, Focus Order.

Не добавляйте tabindex="0" ко всем абзацам, заголовкам и строкам листинга. Это удлинит маршрут, но не поможет прочитать текст. Средства чтения предоставляют собственную навигацию по документу. Дополнительная остановка оправданна для конкретного управления или, в некоторых случаях, самостоятельной прокручиваемой области; такой выбор нужно объяснить её задачей.

Ссылка открывает страницу или фрагмент, кнопка запускает действие. Поэтому оглавление остаётся ссылками, а будущая команда раскрытия меню будет кнопкой. Попытка сделать кнопку из div потребовала бы вручную добавить роль, имя, фокус и обработку клавиш. Встроенный button предоставляет исходное поведение без такого дублирования.

Маршрут, который можно объяснить

Для стенда запишите ожидаемый сценарий: получить фокус на ссылке перехода, активировать её, найти оглавление, открыть раздел о потомках, продолжить движение к следующему полезному действию. Обратное движение через Shift+Tab тоже должно оставаться понятным. Не назначайте число нажатий универсальной нормой: оно зависит от количества реальных элементов страницы.

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

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