Разбираемся со skip link

  • Паттерны
  • HTML
  • CSS

В вебе есть много небольших, но полезных доступных паттернов. И один из них — ссылка для пропуска контента или скип линк (skip link). Это гиперссылка, которая ведёт к основному содержанию страницы и помогает пропустить объёмный, часто повторяющийся контент. Её главная цель — экономия времени пользователей.

Какой контент считается объёмным? Навигационное меню с логотипом и кучей ссылок, громоздкая сложная таблица, буквенные указатели, списки с главами или техническими характеристиками. Чаще всего skip link полезна для пропуска навигации по сайту в хедере.

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

В теории всё просто, но на практике несколько сложнее. Давайте попробуем разобраться со всем по порядку.

Теория

Что говорит про это WCAG 2.1

В руководстве по доступности есть два критерия, связанные со skip link. Первый касается их косвенно, а второй напрямую.

Механизмы для пропуска блоков

Есть два механизма:

  • навигация по ориентирам (landmarks);
  • skip link.

Первый способ доступен для пользователей скринридеров. Ориентиры добавляются с помощью семантических тегов или благодаря ARIA. У второго механизма аудитория больше. Это не только пользователи с особенностями зрения.

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

Кому нужна skip link

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

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

Представьте, что используете для навигации клавиатуру и зашли на сайт интернет-магазина, например, Ozon. Вы нашли нужный товар, перешли к нему и снова оказались в начале страницы. Примерно 40 табов и вот наконец можно узнать больше про понравившийся рюкзак. Со skip link вы бы оказались в нужном месте в одно нажатие и не заснули по пути.

Требования к skip link

  • Находится на первом месте в порядке табуляции.
  • Ведёт сразу к основному контенту и устанавливает фокус на нём. Она эффективнее, если на странице одна основная область — <main>.
  • Может располагаться в основной области страницы. В этом случаем она пропускает нужный блок и ведёт в начало следующего.
  • Понятно называется и хорошо описывает, куда ведёт.
  • Может быть всегда видна, а может появляться при фокусе с клавиатуры. В обоих случаях отвечает критериям WCAG.
  • Можно добавлять несколько таких ссылок. Например, одна ведёт к основному контенту, вторая — к поисковой строке. Не стоит перебарщивать с количеством, иначе в ссылках нет смысла.
  • Не должна мешать пользователям мыши. Это дискуссионное требование, но в нём есть разумное зерно. Если такая ссылка всегда видна, то может запутать пользователей мышки. Они не знакомы с этим паттерном, а ещё один способ прокрутки к основному контенту им не нужен.

Практика

В проекте уже должен поддерживаться фокус с клавиатуры и использоваться семантическая вёрстка. Без этого skip link бесполезна.

Размечаем страницу

Перед тем, как перейти к разметке, пара слов про текст ссылки. На англоязычных сайтах чаще всего используют «Skip to main content» или «Skip to content». Кажется, что самые подходящие эквиваленты в русском — «Перейти к основному контенту» или более краткое «К основному контенту». «Cодержание» — широкое понятие и означает содержимое страницы или оглавление. «Контент» лучше отражает, куда ведёт ссылка.

Ещё несколько вариантов названия:

  • пропустить навигацию (Skip navigation);
  • пропустить основную навигацию (Skip main navigation);
  • пропустить ссылки в навигации (Skip navigation links).

Теперь поговорим о разметке.

Практическая реализация skip link — якорная ссылка. Лучше добавить её в начало <body> или <header>, если это первый элемент на странице. В примерах буду добавлять её в начало хедера.

<header>
    <a href="#main-content" class="skip-link">Перейти к основному контенту</a>
    <!-- Внушительная навигация -->
</header>

А вот куда она должна вести — главная загвоздка. Есть несколько ответов на этот вопрос.

Вариант 1, классический. Ссылка ведёт прямо к <main>.

<!-- Вариант 1 -->

<header>
    <a href="#main-content" class="skip-link">Перейти к основному контенту</a>
    <!-- Внушительная навигация -->
</header>

<main id="main-content">
    <!-- Контент основного блока -->
</main>

Этот вариант более-менее хорошо работает в современных браузерах, но есть одно «но». Могут возникнуть проблемы во всех мобильных браузерах на старых версиях iOS и Android, в старых Chrome и даже в Safari 14. Баги везде разные.

  • iOS + VoiceOver. При переходе по skip link визуально срабатывает прокрутка, но, после свайпа, фокус перемещается на другую область, а не на содержимое основного блока. Другой баг возвращает в начало страницы, когда пытаешься перейти к следующему элементу в основном блоке.
  • Android + TalkBack. Скрытые ссылки просто не получают фокус. Это ошибка всей системы, из-за которой на таких элементах не запускается событие фокуса.
  • Chrome. Фокус остаётся на skip link и перемещается к следующему элементу после ссылки после нажатия на Tab.
  • Safari + VoiceOver. В ишьюс GOV.UK нашла свежий баг с десктопным Safari 14 и VoiceOver. Если нажать на skip link, а потом на клавишу со стрелкой, то фокус переместится на следующий элемент после ссылки.

Баг на iOS исправлен в апреле 2020, на Android — в феврале 2021 и в Chrome — ещё в 2017. Следовательно, они не встречаются с iOS 13+, как минимум с Android 10+ и в старых версиях Chrome. Только часть пользователей скринридеров долго не обновляет браузеры и операционные системы, так что от фикса багов не становится легче.

Вариант 2, в котором ссылка ведёт к <h1> внутри <main>.

<!-- Вариант 2 -->

<header>
    <a href="#main-content" class="skip-link">Перейти к основному контенту</a>
    <!-- Внушительная навигация -->
</header>

<main>
    <h1 id="main-content">Основной заголовок</h1>
    <!-- Остальной контент основного блока -->
</main>

Отличается от первого варианта тем, что скринридеры объявят текст заголовка, а не всё текстовое содержимое <main>. Это даст пользователям больше контроля, так как им не нужно прерывать автоматическое объявление вручную.

У такой разметки те же баги, что и у предыдущего варианта с <main> с атрибутом id.

Вариант 3, который решает проблему двух предыдущих.

<!-- Вариант 3 -->

<header>
    <a href="#main-content" class="skip-link">Перейти к основному контенту</a>
    <!-- Внушительная навигация -->
</header>

<main id="main-content" tabindex="-1">
    <!-- Контент основного блока -->
</main>

Атрибут tabindex с отрицательным значением удаляет элемент из последовательной навигации.

Этот хак хорошо работает со старыми версиями Chrome и на iOS. И снова «но». В других браузерах это может привести к новым багам:

  • При переходе к основному блоку выделяется вся область с отрицательным tabindex.
  • При клике по странице фокус вернётся в её начало.

Здесь на помощь приходит JavaScript. Нужен скрипт, который, после события клика у skip link, устанавливает фокус на main и добавляет ему атрибут tabindex="-1". При потере фокуса этот атрибут удаляется. Можно подсмотреть реализацию в демке Майка Фоскетта, в том числе для Android. Аника Хенке предлагает более универсальное решение на jQuery, которое исправляет все ссылки и тоже удаляет tabindex в момент потери фокуса.

Вариант 4, в котором skip link ведёт к другой ссылке перед <main>.

<!-- Вариант 4 -->

<header>
    <a href="#main-content" class="skip-link">Перейти к основному контенту</a>
    <!-- Внушительная навигация -->
</header>

<a href="#main-content" id="main-content" class="skip-link skip-link-target">Начало основного контента</a>

<main>
    <!-- Контент основного блока -->
</main>

Решение предложил Пол Рэдклифф в «A Deep Dive on Skipping to Content». Он сделал демо для большей наглядности.

В этом случае скринридер объявит, что мы перешли к ссылке «Начало основного контента».

Разметка решает проблему пользователей, которые не понимают, сработала ссылка или нет. Также она помогает избежать багов с некорректным поведением фокуса с клавиатуры в некоторых браузерах.

Этот способ новый и интересный, но вижу несколько проблем.

  1. Вторая ссылка может сбить с толку пользователей скринридеров. Она никуда не ведёт, а так и хочется нажать на неё.
  2. Если пользователь без особенностей зрения протабает всю навигацию, то увидит непонятную ссылку «Начало основного контента».
  3. Точно поймаешь баг на Android из первого варианта, ведь это всё ещё визуально скрытые элементы.

Вариант 5, когда вторая ссылка без текста внутри <main>, с href или без него.

<!-- Вариант 5 -->

<header>
    <a href="#main-content" class="skip-link">Перейти к основному контенту</a>
    <!-- Внушительная навигация -->
</header>

<main>
    <a id="main-content" class="visually-hidden-link"></a>
    <!-- Остальной контент основного блока -->
</main>

Его используют на сайте Deque. И, кажется, это самый проблемный вариант.

Ссылка без href считается ссылкой-плейсхолдером. Её можно использовать для некоторых ситуаций, только если она не должна работать как ссылка. Дело в том, что на неё не устанавливается фокус с клавиатуры.

Если это ссылка без названия, но с href, то скринридеры будут зачитывать содержимое этого атрибута. Например, "#main-content". Для NVDA эту проблему исправит aria-hidden="true".

Вариант 6 с несколькими ссылками, который подходит для редких кейсов.

<!-- Вариант 6 -->

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

<main id="main-content">
    <!-- Контент основного блока -->
</main>

В этом примере обе ссылки обёрнуты в дополнительный <nav> с aria-label. Скринридеры объявят, что это навигация со ссылками для пропуска меню. Можно дополнительно обернуть их в <ul>, чтобы пользователям было проще навигироваться.

Без хака с tabindex="-1" из третьего варианта тоже могут возникнуть проблемы в старых и некоторых новых браузерах.

Скрываем ссылку

Визуально скрывать ссылку и показывать её при фокусе тоже можно разными способами. Есть несколько основных правил:

  • Не используйте свойства display: none, visibility: hidden или атрибут hidden. Нам надо скрыть ссылку только визуально.
  • Не устанавливайте значение 0 для width и height. Тогда фокус просто не установится на таком элементе.

Давайте рассмотрим пару конкретных примеров со стилями.

Вариант 1 с position: absolute и безумным отрицательным значением left.

Мы просто абсолютно позиционируем элемент, выносим его за видимую область, а при фокусе возвращаем туда, куда нужно.

/* Вариант 1 */

.skip-link {
    position: absolute;
    top: auto;
    left: -999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* Показываем при фокусе */
.skip-link:focus {
    top: 0;
    left: 0;
    width: auto;
    height: auto;
    overflow: visible;
}

Вариант 2 с clip или clip-path. Старый-добрый visually-hidden способ.

Можно использовать их одновременно для большей совместимости. Ещё встречала вариант с clip-path: inset(50%).

/* Вариант 2 */

.skip-link {
    position: absolute;
    margin: 0;
    padding: 0;

    /* Для всех браузеров */
    clip: rect(0 0 0 0);

    /* Более современный способ. Поддерживается с префиксом */
    -webkit-clip-path: polygon(0 0, 0 0, 0 0, 0 0);
    clip-path: polygon(0 0, 0 0, 0 0, 0 0);
}

/* Показываем при фокусе */
.skip-link:focus {
    top: 0;
    left: 0;
    width: auto;
    height: auto;

    /* Если используете clip */
    clip: auto;

    /* Если используете clip-path */
    -webkit-clip-path: none;
    clip-path: none;
}

Свойство clip устарело, и ему на смену должно прийти clip-path. Пока будущее не наступило окончательно, и clip-path в большинстве браузеров поддерживается с префиксом.

Вариант 3 с transform.

Снова позиционируем ссылку абсолютно и прячем её за пределы видимой области с помощью transform. Когда на ней фокус, то возвращаем обратно.

/* Вариант 3 */

.skip-link {
    position: absolute;
    top: 0;
    left: 0;
    transform: translateY(-100%);
}

/* Показываем при фокусе */
.skip-link:focus {
    transform: translateY(0%);
}

Добавляем последние штрихи

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

WebAIM рекомендует не показывать ссылку неожиданно. Это может сбить с толку пользователей клавиатуры, которые видят интерфейс. Это исправит плавная анимация. Тогда ссылка будет выезжать из-за края экрана и уезжать обратно, когда на ней больше нет фокуса.

Размещать ссылки можно в любой верхней части экрана. Очень часто их располагают в левом верхнем углу, но это не железное правило.

Собрала небольшой список сайтов со skip link, где можно посмотреть, как они задизайнены у других. Используйте для навигации Tab на Windows и Tab или Option+Tab на macOS.

Пара слов напоследок

Часто, чем что-то кажется проще, тем оно сложнее на самом деле. Это произошло и со skip link.

Вариантов того, как сделать skip link, довольно много для такого небольшого элемента. У них есть плюсы и минусы. Для себя выбрала бы классическую реализацию со ссылкой, которая ведёт к <main> или <h1> с tabindex="-1" из третьего варианта. И прогрессивно улучшила её с помощью JS. Что касается стилей для скрытия ссылки, то они все рабочие. Можно выбрать любой, который больше подходит для проекта. Я чаще всего пользуюсь абсолютным позиционированием и отрицательным значением left.

Что почитать


Спасибо Василию Дудину за помощь с редактированием.

Другие посты