SST-A - Единый стандарт для табов

SST-A - Единый стандарт для табов
Стандарты

62

6 мин.

Определения

Стандарт SST-A определяет требования к семантике, доступности и логике работы табов.

Структура и поведение

Контейнер и прокрутка

  • Вкладки располагаются в одну строку. По соглашению SST-A их не переносят на следующую строку и не заменяют выпадающим списком на узких экранах.
  • Для полосы вкладок создаётся отдельный контейнер с overflow-x: auto. Горизонтально прокручивается только полоса, а не вся страница; содержимое открытой панели должно адаптироваться к ширине экрана.
  • Элементы следующего ряда не создаются. Вкладки сохраняют свою ширину, а контейнер обеспечивает горизонтальную прокрутку при переполнении.
  • По возможности у края контейнера оставляется видимая часть следующей вкладки — ориентировочно 10–20px. Это визуально показывает продолжение списка и может избавить от декоративных стрелок-индикаторов. Нельзя обрезать подпись или индикатор фокуса активной вкладки; само визуальное указание не заменяет клавиатурную навигацию.
  • На мобильных устройствах системную полосу прокрутки можно скрыть, если возможность прокрутки остаётся понятной благодаря частично видимой следующей вкладке или другому индикатору. Полоса прокрутки не скрывается таким образом, чтобы прокрутка стала недоступной с клавиатуры.
  • -webkit-overflow-scrolling: touch не требуется считать базовым условием плавной прокрутки. В современных версиях iOS и iPadOS ускоренная прокрутка областей с overflow работает без этого свойства; на iPhone оно также может создавать контекст наложения. Свойство добавляется только для явно поддерживаемых старых версий после проверки на устройствах.

Активная вкладка и центрирование

  • Активная вкладка имеет заметное визуальное состояние, например акцентную линию или изменение фона. Различие не должно передаваться только цветом.

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

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

  • Для направления LTR целевое значение можно рассчитать относительно начала прокручиваемого содержимого:

    targetScrollLeft = clamp(tabStart + tabWidth / 2 - viewportWidth / 2, 0, scrollWidth - viewportWidth)

    Здесь tabStart — позиция вкладки относительно начала полосы прокрутки. Для RTL-разметки учитываются направление письма и различия поведения горизонтальной прокрутки; при возможности используется логическая прокрутка элемента.

Идентификация и URL

Связи табов и панелей

  • Контейнеру назначается role="tablist" и доступное имя через видимый заголовок с aria-labelledby или через aria-label.
  • Каждый переключатель панели получает role="tab", уникальный стабильный id и aria-controls="panel-id", указывающий на связанную панель.
  • Каждая панель получает role="tabpanel", уникальный id и aria-labelledby="tab-id", связывающий её с управляющей вкладкой.
  • Активная вкладка имеет aria-selected="true", остальные — aria-selected="false". Неактивные панели скрываются, например атрибутом hidden.
  • Вкладки реализуют roving tabindex: активная вкладка имеет tabindex="0", остальные — tabindex="-1". Для панели без фокусируемого содержимого может задаваться tabindex="0", чтобы она была достижима в последовательности клавиатурной навигации.

Хеш и прямые ссылки

  • Каждой вкладке назначается уникальный стабильный идентификатор, который можно безопасно использовать во фрагменте URL, например #tab-id. Идентификаторы задаются в URL-safe формате либо кодируются и декодируются одинаково при чтении хеша.
  • При первоначальной загрузке страницы хеш сопоставляется с вкладкой. Если соответствующая вкладка найдена, активируются она и её панель, после чего полоса прокручивается к этой вкладке. Если хеш неизвестен, остаётся вкладка по умолчанию.
  • При выборе вкладки URL обновляется в соответствии с выбранной стратегией истории. history.replaceState обновляет текущую запись и не добавляет отдельный шаг «Назад» для каждого переключения. Приложение должно обновлять состояние вкладки в том же обработчике, а не полагаться на событие hashchange.
  • Если переходы между вкладками должны восстанавливаться кнопками «Назад» и «Вперёд», используется history.pushState; состояние вкладки синхронизируется при событии popstate.
  • Обновление хеша не должно удалять путь или параметры запроса. При обработке неизвестного или некорректного хеша интерфейс сохраняет работоспособную вкладку по умолчанию.

Доступность и клавиатура

  • Роли tablist, tab и tabpanel применяются только для набора вкладок, переключающих панели на той же странице. Для навигации между страницами используются ссылки, даже если визуально они оформлены как вкладки.
  • Клавиша Tab переводит фокус на активную вкладку внутри полосы и затем выводит его из списка табов к содержимому панели или следующему элементу страницы.
  • В горизонтальном списке стрелки ← и → перемещают фокус между вкладками; при достижении края переходят к противоположному концу списка. В интерфейсах RTL направление клавиш согласуется с порядком и направлением чтения.
  • Клавиши Home и End могут переводить фокус к первой и последней вкладке. При ручной активации выбранная вкладка активируется клавишей Enter или Space; при автоматической активации выбор следует за фокусом.
  • Автоматическая активация применяется, когда панель отображается без заметной задержки. При медленной загрузке панелей предпочтительна ручная активация, чтобы перемещение фокуса по вкладкам не запускало задерживающие операции.
  • При перемещении фокуса активная вкладка прокручивается в видимую область. Видимый индикатор фокуса не обрезается контейнером.

Тестирование

  • На мобильной ширине вкладки остаются в одной строке, полоса прокручивается касанием, а горизонтальный скролл не распространяется на всю страницу.
  • Частично видимая следующая вкладка или другой индикатор ясно показывает возможность прокрутки. Скрытие scrollbar не лишает интерфейс понятной обратной связи.
  • Активная вкладка визуально и программно обозначена; значения aria-selected, связи aria-controls и aria-labelledby, а также видимость панелей синхронизированы.
  • У списка вкладок есть доступное имя, а у каждой вкладки — понятная подпись; связи между элементами проверяются со скринридером.
  • Клавиатурная навигация соответствует выбранному режиму активации, работает на обоих концах списка и учитывает направление RTL.
  • Выбранная вкладка остаётся видимой после клика, клавиатурного перехода и загрузки по хешу; у первой и последней вкладки проверяется допустимое ограничение центрирования.
  • Прямые ссылки с корректным и неизвестным хешем проверяются при загрузке и обновлении страницы.
  • Если применяется replaceState, переключение обновляет адрес, не добавляя запись истории для каждой вкладки. Если применяется pushState, переходы «Назад» и «Вперёд» синхронизируют URL, активную вкладку и панель.
  • Скроллбар скрывается только при сохранённой прокрутке касанием и клавиатурой; отображение и поведение проверяются в целевых браузерах, включая iOS и Android.
  • Центрирование и плавная прокрутка не мешают пользователям, включившим prefers-reduced-motion: reduce.

Примеры

Полоса вкладок с панелями и связями ARIA:

<div class="tabs__viewport">
  <div class="tabs__list" role="tablist" aria-label="Разделы документа">
    <button
      id="tab-overview"
      type="button"
      role="tab"
      aria-controls="panel-overview"
      aria-selected="true"
      tabindex="0"
    >
      Обзор
    </button>
    <button
      id="tab-details"
      type="button"
      role="tab"
      aria-controls="panel-details"
      aria-selected="false"
      tabindex="-1"
    >
      Подробности
    </button>
  </div>
</div>

<section
  id="panel-overview"
  role="tabpanel"
  aria-labelledby="tab-overview"
  tabindex="0"
>
  Содержимое раздела «Обзор».
</section>

<section
  id="panel-details"
  role="tabpanel"
  aria-labelledby="tab-details"
  tabindex="0"
  hidden
>
  Содержимое раздела «Подробности».
</section>

CSS для однострочной прокручиваемой полосы:

.tabs__viewport {
  overflow-x: auto;
  padding: 2px;
  scroll-behavior: smooth;
}

.tabs__list {
  display: flex;
  flex-wrap: nowrap;
  width: max-content;
  min-width: 100%;
}

.tabs__list [role="tab"] {
  flex: 0 0 auto;
  white-space: nowrap;
}

.tabs__list [role="tab"][aria-selected="true"] {
  border-block-end: 2px solid currentColor;
}

.tabs__list [role="tab"]:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 2px;
}

@media (max-width: 767px) {
  .tabs__viewport {
    scrollbar-width: none;
  }

  .tabs__viewport::-webkit-scrollbar {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .tabs__viewport {
    scroll-behavior: auto;
  }
}

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

Ширина 767px в медиазапросе — примерная точка переключения, а не требование SST-A. Она заменяется на breakpoint, принятый в адаптивной системе проекта.

Центрирование вкладки и синхронизация URL:

const centerTab = (tab) => {
  tab.scrollIntoView({
    block: 'nearest',
    inline: 'center'
  })
}

const findTabFromUrl = (tabs) => {
  const tabId = window.location.hash.slice(1)

  return tabs.find((tab) => tab.id === tabId) || null
}

const updateTabUrl = (tab) => {
  window.history.replaceState(null, '', '#' + tab.id)
}

При инициализации найденная через findTabFromUrl вкладка активируется вместе со своей панелью и передаётся в centerTab. При пользовательском выборе сначала обновляются aria-selected, roving tabindex и видимость панелей, затем вызываются centerTab и updateTabUrl. Этот фрагмент показывает синхронизацию состояния, URL и прокрутки; обработчики клавиатуры реализуются по правилам выше.

Похожие категории:

Похожие статьи

  • Анатомия React компонента

    Моё видение того, как должен выглядеть React компонент

    421

    7 мин.

  • SSF-U - Единый стандарт для фуллскрина

    Стандарт SSF-U определяет требования к семантике, доступности и логике работы фуллскрина

    248

    2 мин.

  • SSA - Единый стандарт для аккордеона

    Стандарт SSA определяет требования к семантике, доступности и логике работы аккордеона

    258

    2 мин.

  • Bad Practices для сайтов

    Разбор критических ошибок веб-дизайна. Почему слайдеры, автоплей и тяжёлые страницы снижают конверсию и позиции в Google и Yandex

    232

    1 мин.

  • SSP - Единый стандарт для пагинации

    Стандарт SSP определяет требования к семантике, доступности и логике работы пагинации

    240

    1 мин.

  • SSPS - Единый стандарт для структуры проекта

    Стандарт SSPS определяет требования к структуре и наименованию файлов и папок в проекте

    345

    2 мин.

  • Все статьи

Свяжитесь со мной