Skip link

Удобный пропуск ссылок при навигации с помощью клавиатуры.

Время чтения: меньше 5 мин

Кратко

Секция статьи "Кратко"

Skip link (далее скип-линк) — специальный элемент на странице в виде гиперссылки, который помогает быстро перейти к контенту при использовании клавиатуры, пропустив блок с навигацией.

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

Пример

Секция статьи "Пример"

Для примера давайте откроем страницу задачи по добавлению доки про скип-линк. У GitHub вверху расположено меню, в котором есть строка поиска, ссылка на страницу пулреквестов и много чего ещё. Если мы будем переходить по разным страницам, то придётся кучу раз нажать Tab, чтобы добраться до контента.

К счастью, GitHub добавляет на страницы скип-линк. Если нажать Tab, когда страница открылась и мы находимся в самом верху, то в верхнем левом углу появится блок с надписью «Skip to content». Если теперь нажмём Enter, то перейдём сразу к контенту. В нашем случае это будет интерфейс репозитория.

Скип-линк в интерфейсе GitHub

Как реализовать

Секция статьи "Как реализовать"

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

Допустим, у нас есть сайт с простой навигацией:

Открыть демо в новой вкладке

Если будем использовать клавиатуру для навигации, то придётся 5 раз нажать Tab, чтобы добраться до основного контента. И так придётся делать на каждой новой странице.

Давайте добавим скип-линк, чтобы упросить навигацию. Для этого в самое начало <body> перед блоком навигации добавим ссылку, которая видна только при фокусе.

        
          
          <body>  <a class="skip-link" href="#main">    Перейти к контенту  </a>  <!-- Навигация -->  <main id="main">    <!-- Остальной контент страницы -->  </main></body>
          <body>
  <a class="skip-link" href="#main">
    Перейти к контенту
  </a>
  <!-- Навигация -->
  <main id="main">
    <!-- Остальной контент страницы -->
  </main>
</body>

        
        
          
        
      

Заметьте, что мы скрываем ссылку при помощи transform: translateY(-100%). Просто сделать display: none нельзя, тогда скринридеры её проигнорируют.

        
          
          .skip-link {  display: block;  position: absolute;  top: 0;  left: 0;  padding: 10px 15px;  background: #F498AD;  transform: translateY(-100%);}
          .skip-link {
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  padding: 10px 15px;
  background: #F498AD;
  transform: translateY(-100%);
}

        
        
          
        
      

Когда ссылка в фокусе, покажем её, вернув значение по умолчанию transform:

        
          
          .skip-link:focus {  transform: translateY(0);}
          .skip-link:focus {
  transform: translateY(0);
}

        
        
          
        
      
Открыть демо в новой вкладке

Теперь на странице сразу же появится скип-линк, когда нажмём Tab. С её помощью можем сразу перейти к контенту, не тратя время на навигацию. Если всё-таки интересует какой-то пункт в меню, можем нажать Tab ещё раз. Тогда скип-линк скроется, а фокус окажется на первом интерактивном элементе на странице.

На практике

Секция статьи "На практике"

Татьяна Фокина советует

Секция статьи "Татьяна Фокина советует"

🛠 С этой и другими реализациями скип-линк есть проблемы во всех мобильных браузерах в iOS до 13 версии, Android до 10 версии, а ещё в некоторых версиях Chrome и в Safari 14.

В каких-то браузерах при свайпе, нажатии на Tab или на клавишу со стрелкой фокус перемещается не на контент, к которому ведёт ссылка, а на следующий элемент после ссылки. Где-то скип-линк вообще не получает фокус.

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

Для решения проблемы добавьте к <main> или к другому блоку, куда ведёт скип-линк, tabindex="-1". Атрибут tabindex с отрицательным значением удаляет элемент из последовательной навигации.

        
          
          <body>  <a class="skip-link" href="#main">    Перейти к контенту  </a>  <!-- Навигация -->  <main id="main" tabindex="-1">    <!-- Остальной контент страницы -->  </main></body>
          <body>
  <a class="skip-link" href="#main">
    Перейти к контенту
  </a>
  <!-- Навигация -->
  <main id="main" tabindex="-1">
    <!-- Остальной контент страницы -->
  </main>
</body>

        
        
          
        
      

Из-за этого возникнут побочные эффекты. При переходе к основному блоку теперь выделяется вся область с отрицательным tabindex, а при клике по странице фокус вернётся в её начало. Это исправит JavaScript. После события клика у скип-линк нужно установить фокус на теге, к которому ведёт ссылка, и добавить к нему tabindex="-1". При потере фокуса этот атрибут нужно удалить.

Один из вариантов решения проблемы для десктопных браузеров.

        
          
          (_ => {  const skip_lnk = document.querySelector('.skip_lnk');  if (!skip_lnk) return false;  skip_lnk.addEventListener('click', e => {    e.preventDefault();    const to_obj = document.getElementById(skip_lnk.href.split('#')[1]);    if (to_obj) {      to_obj.setAttribute('tabindex', '-1');      to_obj.addEventListener('blur', _ => {        to_obj.removeAttribute('tabindex');      }, {once: true});      to_obj.focus();    }  });})();
          (_ => {
  const skip_lnk = document.querySelector('.skip_lnk');

  if (!skip_lnk) return false;
  skip_lnk.addEventListener('click', e => {
    e.preventDefault();
    const to_obj = document.getElementById(skip_lnk.href.split('#')[1]);
    if (to_obj) {
      to_obj.setAttribute('tabindex', '-1');
      to_obj.addEventListener('blur', _ => {
        to_obj.removeAttribute('tabindex');
      }, {once: true});
      to_obj.focus();
    }
  });
})();

        
        
          
        
      

Вариант решения проблемы с багом у TalkBack на Android.

        
          
          (_ => {  const skip_lnk = document.querySelector('.skip_lnk');  if (!skip_lnk) return false;  skip_lnk.addEventListener('click', e => {    e.preventDefault();    const to_obj = document.getElementById(skip_lnk.href.split('#')[1]);    if (to_obj) to_obj.focus();  });})();
          (_ => {
  const skip_lnk = document.querySelector('.skip_lnk');

  if (!skip_lnk) return false;
  skip_lnk.addEventListener('click', e => {
    e.preventDefault();
    const to_obj = document.getElementById(skip_lnk.href.split('#')[1]);
    if (to_obj) to_obj.focus();
  });

})();

        
        
          
        
      

🛠 Когда у вас несколько скип-линк, можно обернуть их в <nav> и назвать эту группу с помощью aria-label. Тогда пользователи скринридеров могут использовать эту область как ориентир и быстро перемещаться к ней с помощью клавиш или через меню с ориентирами.

        
          
          <header>  <nav aria-label="Ссылки для пропуска меню">    <a href="#search" class="skip-link">Перейти к поиску</a>    <a href="#main" class="skip-link">Перейти к содержимому</a>  </nav>  <nav aria-label="Главная">    <!-- Основная навигация -->  </nav>  <form id="search">    <!-- Поиск по сайту -->  </form></header><main id="main">  <!-- Остальной контент страницы --></main>
          <header>
  <nav aria-label="Ссылки для пропуска меню">
    <a href="#search" class="skip-link">Перейти к поиску</a>
    <a href="#main" class="skip-link">Перейти к содержимому</a>
  </nav>
  <nav aria-label="Главная">
    <!-- Основная навигация -->
  </nav>
  <form id="search">
    <!-- Поиск по сайту -->
  </form>
</header>

<main id="main">
  <!-- Остальной контент страницы -->
</main>

        
        
          
        
      

На собеседовании

Секция статьи "На собеседовании"

Это партнёрская рубрика, мы выпускаем её совместно с сервисом онлайн-образования Яндекс Практикум. Приносите вопрос, на который не знаете ответа, в задачи, мы разложим всё по полочкам и опубликуем. Если знаете ответ, присылайте пулреквест на GitHub.

Это вопрос без ответа. Вы можете помочь! Почитайте о том, как контрибьютить в Доку.