Как работает focusgroup и чем полезен?

• html

Введение

В последнее время активно разрабатывается новое АПИ про фокус и работу с клавиатурой. Если события beforefocus / focusreset и перемещения фокуса вперёд / назад ещё только в проработке, то новый атрибут focusgroup уже с нами с Хрома 150, а в данный момент в проработке находятся доработки к нему.

Что это за атрибут и в каких случаях может использоваться?

Задача

<div focusgroup="toolbar" aria-label="Форматирование текста">
    <button type="button">Жирный</button>
    <button type="button">Курсив</button>
    <button type="button">Подчёркивание</button>
</div>

При создании компонентов управления (контролов) часто встаёт задача сделать тулбар, меню и прочие группы вкладок. Обычно у подобных элементов есть стандартный подход к управлению с клавиатуры: у меню поддержка стрелок вверх-вниз, у вкладок – поддержка стрелок влево-вправо и так далее.

Чтобы всё это реализовать, обычно нужно исключить все фокусируемые элементы из общего обхода (через tabindex="-1") и навесить обработчиков на клавиатурное нажатия, перемещая фокус между элементами. Ну а активному элементу выставлять tabindex="0".

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

Учитывая, что все эти стандартные подходы в вебе встречаются не раз и не два, разработчики браузеров наконец представили нам “стандартный” способ сделать подобные элементы интерфейса через новый атрибут: focusgroup.

Focusgroup

Допустимые значения: toolbar, tablist, radiogroup, listbox, menu, menubar, none.

Как можно заметить, значения повторяют типичные интерфейсные шаблоны. Что даёт этот атрибут:

  1. Работу фокуса. При обычном обходе через Tab фокус получит только один элемент, а остальные будут доступны через стрелки
  2. Семантику для скринридеров. Причём это касается как контейнера, так и дочерних элементов
  3. Сохранение последнего элемента при обходе
  4. Различные пресеты настроек в зависимости от выбранного значения

Важный нюанс. Атрибут не влияет на выбор элемента, только на фокус! Это значит, что aria-selected и подобными вещами всё ещё нужно управлять самому!

Посмотрим более подробно на все функции. Кстати, есть большой список примеров, при желании можно обращаться к нему.

Направление контента

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

  • inline – дочерние элементы расположены по инлайновой оси (в нашем представлении это обычно горизонталь)
  • block – дочерние элементы вдоль блочной оси

Итоговая конструкция выглядит так: focusgroup="toolbar inline".

Горизонтальный тулбар (inline)

Вертикальный тулбар (block)

Разные значения focusgroup имеют разные предустановки с направлениями. toolbar – инлайновое направление, menu – блочное.

Зацикленный обход

Ещё один модификатор – wrap. Он включён по умолчанию для значения tablist. Тем самым, при обходе элементов пользователь не упрётся в первый / последний элемент. Фокус в этом случае зацикленный.

Если же вы используете tablist, но вы хотите выключить зацикленный фокус, можно указать модификатор nowrap.

<div focusgroup="tablist nowrap">
    ...
</div>

Стартовый элемент

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

<div focusgroup="tablist">
    <button>Первая</button>
    <button focusgroupstart>Вторая</button>
    <button>Третья</button>
</div>

При переходе фокуса в группу (как вперед через Tab, так и назад через Shift+Tab) фокус получит элемент с атрибутом focusgroupstart. Это может быть полезно, если выбран не первый элемент в группе (активная вкладка в момент загрузки страницы).

Сохранение выбранного элемента

По умолчанию, focusgroup запоминает активный элемент при уходе через Tab / Shift+Tab. Чтобы этого избежать, можно использовать модификатор nomemory:

<div focusgroup="tablist nomemory">
    ...
</div>

Вложенные фокусгруппы

В более сложных случаях может потребоваться вложенная группа контролов:

<div focusgroup="toolbar">
    <button>Кнопка 1</button>
    <div focusgroup="toolbar">
        <button>Вложено 1</button>
        <button>Вложено 2</button>
    </div>
    <button>Кнопка 2</button>
</div>

При таком использовании вложенная группа выступает, как большой элемент. Для перехода “внутрь” нужно использовать Tab, а не стрелки (и не Enter). А при перемещении табом через все элементы фокусируются элемент перед вложенной группой, элемент внутри вложенной группы, и элемент после вложенной группы. Это поведение выглядит не очень очевидно.

Исключение элементов из группы

С помощью значения focusgroup="none" можно исключить элементы из группы:

<div focusgroup="toolbar">
    <button>Кнопка</button>
    <div focusgroup="none">
        <button>Кнопка</button>
        <button>Кнопка</button>
    </div>
    <button>Кнопка</button>
</div>

Это приводит к тому, что элементы внутри focusgroup="none" не получают фокус стрелками – их нужно фокусить через Tab.

Интеграция с reading-flow

CSS свойство reading-flow позволяет изменить порядок фокуса (например, в случае смены направления или использования order во флексбоксах).

Атрибут focusgroup гармонично интегрирован с reading-flow: порядок обхода группы такой же, как если бы был без группы.

Что дальше?

Файерфокс позитивно смотрит на focusgroup, а Сафари пока без ответа.

Но уже сейчас есть подвижки в сторону дополнительного функционала (FocusGroup V2):

  • Модификатор itemcontrols влияет на доступ к дочерним элементам
  • Пресеты feed и grid

Пока что эти возможности нигде не реализованы.

Итого

HTML делает огромный шаг навстречу типовым контролам. Количество кода, которое нужно для создания интерфейса, сокращается, а значит, и среднее качество интерфейсов в Интернете вырастет.

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

Ждём в Baseline 2027? Не уверен, особенно с Сафари.

Обсудить в Telegram

Почитать ещё посты

  • HTML в Canvas вышел, что он нам несёт?
  • Анимированная выпадушка на CSS
  • frame-sizing не решает проблемы размера ифреймов?
  • Маркировка контента, сделанного с помощью ИИ
  • Отслеживание поддержки фич