Запрос отзывов от разработчиков: фокус-группа

Жак Ньюман
Jacques Newman

Опубликовано: 5 марта 2026 г.

Атрибут ` focusgroup в HTML — это предлагаемый декларативный способ добавления навигации с помощью клавиш со стрелками к составным виджетам, таким как панели инструментов, списки, меню, списки и т. д., без написания какого-либо JavaScript-кода с использованием `roving-tabindex`. Один атрибут заменяет сотни строк шаблонного кода. Нам нужна ваша обратная связь перед выпуском этого решения.

Попробуйте и поделитесь своим мнением.

Вы можете опробовать focusgroup уже сегодня в Chrome, Edge и других браузерах на основе Chromium, включив его одним из двух способов:

  1. Локальное тестирование: В браузере откройте страницу about://flags и включите флаг экспериментальных функций веб-платформы . Или запустите браузер из командной строки, используя параметр командной строки --enable-blink-features=Focusgroup .
  2. Пробная версия Origin: Зарегистрируйтесь для участия в пробной версии Origin от focusgroup , чтобы протестировать ее на своем сайте с реальными пользователями.

Затем изучите интерактивные демонстрации , чтобы увидеть каждый узор в действии.

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

Это кроссбраузерная разработка: предложение исходит от Microsoft через сообщество OpenUI при активной поддержке Google. Форма API может измениться в зависимости от ваших отзывов. Давайте разберемся, какую проблему решает focusgroup и как работает API.

Проблема: ручная прокрутка табиндекса

Если вы когда-либо создавали панель инструментов, список вкладок, меню или список, вы наверняка писали какой-то вариант этого кода. Руководство по разработке ARIA-интерфейсов (APG) рекомендует, чтобы составные виджеты отображали одну точку перехода по вкладке и позволяли пользователям перемещаться между элементами с помощью клавиш со стрелками. Этот шаблон известен как «перемещающийся tabindex». Многие UI-фреймворки переписывают его с нуля:

<div role="toolbar" aria-label="Text formatting" id="toolbar">
  <button type="button" tabindex="0">Bold</button>
  <button type="button" tabindex="-1">Italic</button>
  <button type="button" tabindex="-1">Underline</button>
  <button type="button" tabindex="-1">Strikethrough</button>
</div>

Далее разработчикам необходимо использовать JavaScript, который отслеживает нажатия клавиш со стрелками для перемещения фокуса, и настроить атрибут tabindex для всех элементов. Это упрощенная версия. В производственной реализации также необходимо учитывать следующее:

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

Большинство библиотек пользовательского интерфейса, включая React , Angular CDK и Fluent UI, поставляют свои собственные версии этой логики. Это приводит к дублированию усилий ради того, чтобы получить нечто, что могло бы стать базовым элементом платформы.

Решение: атрибут focusgroup

При использовании focusgroup та же панель инструментов становится следующей:

<div focusgroup="toolbar" aria-label="Text formatting">
  <button type="button">Bold</button>
  <button type="button">Italic</button>
  <button type="button">Underline</button>
  <button type="button">Strikethrough</button>
</div>
В фокусе находится строка меню с выделенной курсивом кнопкой.

Попробуйте вживую: Шаблон панели инструментов > Базовая панель инструментов . Вот и всё. Никакого JavaScript для навигации с помощью клавиш со стрелками. Никакого ручного управления tabindex. Вот что теперь делает для вас браузер:

  • Навигация с помощью клавиш со стрелками: перемещайтесь между элементами, соблюдая режим ввода и направление движения.
  • Единая вкладка: браузер автоматически сворачивает участвующие элементы в одну вкладку. Разработчикам не нужно устанавливать tabindex="-1" для неактивных элементов.
  • Память последнего сфокусированного элемента: когда пользователь покидает фокусную группу и возвращается, фокус восстанавливается на элементе, который он покинул.
  • Семантика ARIA: браузер предоставляет соответствующие роли (например, role="toolbar" ) в зависимости от выбранного поведения при использовании универсальных элементов.

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

Обзор API

Атрибут focusgroup принимает список токенов, разделенных пробелами. Первый токен всегда является токеном поведения , который объявляет шаблон виджета. Далее следуют необязательные токены-модификаторы: focusgroup="<behavior> [inline|block] [wrap] [nomemory]" .

Токены поведения

Токен поведения обязателен (если none используется значение «нет», чтобы отказаться от участия в родительской фокус-группе). Он определяет шаблон составного виджета, гарантируя, что правильные роли могут быть определены автоматически, если не указано иное. Токены соответствуют шаблонам, описанным в руководстве по разработке Aria , и перечислены в следующей таблице:

Поведение Шаблон APG Минимальная роль контейнера (при наличии) Минимальная роль ребенка
(при применении)
Модификаторы по умолчанию
toolbar Панель инструментов панель инструментов (никто) inline
tablist Вкладки APG список вкладок вкладка inline wrap
radiogroup Радиогруппа радиогруппа радио (никто)
listbox Список список вариант (никто)
menu Меню меню пункт меню block wrap
menubar Панель меню панель меню пункт меню inline wrap
none н/д н/д н/д н/д

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

Ограничение по осям ( inline и block )

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

  • inline : Фокус-группа реагирует только на клавиши со стрелками по встроенной оси, влево и вправо в большинстве контекстов английского языка ( горизонтально, сверху вниз) .
  • block : Фокус-группа реагирует только на клавиши со стрелками по оси блока, вверх и вниз в большинстве контекстов английского языка (горизонтально, сверху вниз).

Ограничение по оси соответствует логическим свойствам CSS и автоматически адаптируется к режиму и направлению письма.

Навигация по кругу

По умолчанию навигация с помощью клавиш со стрелками останавливается на краях группы фокуса. Добавьте модификатор wrap , чтобы цикл повторялся от последнего элемента к первому (и от первого к последнему). Если в поведении по умолчанию включено переключение между элементами (wrap), используйте модификатор nowrap , чтобы отключить это поведение.

Попробуйте вживую: Шаблон списка вкладок > Горизонтальный список вкладок с переносом . В этом примере, когда фокус находится на вкладке «Часто задаваемые вопросы» и пользователь нажимает клавишу со стрелкой вправо, фокус возвращается на вкладку «Обзор» .

Атрибут focusgroupstart

Атрибут focusgroupstart указывает, какой элемент получит фокус при первом переходе в группу элементов с помощью клавиши Tab (или каждый раз, когда память отключена):

<div focusgroup="toolbar nomemory" aria-label="Entry point demo">
  <button type="button">First</button>
  <button type="button" focusgroupstart>Middle (Entry)</button>
  <button type="button">Last</button>
</div>
Панель меню с центральной кнопкой в ​​фокусе.

Нажатия Tab и Shift+Tab приводят к точке "Middle (Entry)", поскольку она имеет focusgroupstart , а память отключена с помощью модификатора nomemory . Попробуйте вживую: Шаблон панели инструментов > Точка входа с focusgroupstart .

Отключить память (nomemory)

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

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

Отказаться от участия ( focusgroup="none" )

Используйте focusgroup="none" , чтобы исключить элемент и его поддерево из навигации стрелками родительской группы фокуса. Исключенный элемент и его поддерево остаются доступными с помощью клавиши Tab, но клавиши со стрелками пропускают их:

<div focusgroup="toolbar" aria-label="Segmented toolbar">
  <button type="button">New</button>
  <button type="button">Open</button>
  <button type="button">Save</button>
  <span focusgroup="none">
    <button type="button">Help</button>
    <button type="button">Shortcuts</button>
  </span>
  <button type="button">Close</button>
  <button type="button">Exit</button>
</div>
Меню, в котором кнопки «Справка» и «Сочетания клавиш» неактивны (затемнены).

Использование клавиши со стрелкой вправо позволяет перейти к пунктам «Создать», «Открыть», «Сохранить», «Закрыть» и «Выход», минуя кнопки «Справка» и «Сочетания клавиш». Однако пользователь по-прежнему может перейти в раздел справки с помощью клавиши Tab, чтобы получить доступ к этим кнопкам. Попробуйте вживую: Дополнительные концепции > Сегменты отказа от участия с focusgroup="none" .

Общие закономерности

Список таблиц

Элемент управления вкладками с навигацией между вкладками с помощью клавиш со стрелками.

<div focusgroup="tablist nomemory" aria-label="Sections">
  <button type="button" aria-selected="true" aria-controls="panel-overview" id="tab-overview" focusgroupstart>Overview</button>
  <button type="button" aria-selected="false" aria-controls="panel-features" id="tab-features">Features</button>
  <button type="button" aria-selected="false" aria-controls="panel-pricing" id="tab-pricing">Pricing</button>
  <button type="button" aria-selected="false" aria-controls="panel-faq" id="tab-faq">FAQ</button>
</div>
<div role="tabpanel" id="panel-overview" aria-labelledby="tab-overview" tabindex="0">...</div>
<div role="tabpanel" id="panel-features" aria-labelledby="tab-features" tabindex="0">...</div>
<div role="tabpanel" id="panel-pricing" aria-labelledby="tab-pricing" tabindex="0">...</div>
<div role="tabpanel" id="panel-faq" aria-labelledby="tab-faq" tabindex="0">...</div>
В данный момент активна вкладка «Обзор».

Попробуйте вживую: Шаблон списка таблиц > Горизонтальный список таблиц с переносом строк .

На что следует обратить внимание:

  • Атрибут focusgroupstart находится на выбранной вкладке, поэтому фокус всегда переходит туда.
  • Модификатор nomemory гарантирует, что даже если пользователь ранее фокусировался на другой вкладке, повторный вход всегда будет осуществляться на выбранную вкладку.
  • Модификатор inline ограничивает навигацию стрелками только клавишами влево и вправо. Это соответствует ожидаемому поведению, описанному в шаблоне APG Tabs .
  • Модификатор wrap позволяет пользователям непрерывно использовать клавиши со стрелками во всех вкладках.
  • Код разработчика, опущенный для краткости, обрабатывает фактическое выделение: обновление атрибута aria-selected , переключение видимости панели и перемещение атрибута focusgroupstart при изменении выделения.

Простое вертикальное меню с навигацией с помощью стрелок вверх и вниз.

<div focusgroup="menu" aria-label="File actions" class="menu-vertical">
    <button type="button" class="menu-item">New</button>
    <button type="button" class="menu-item">Open…</button>
    <button type="button" class="menu-item">Save</button>
    <button type="button" class="menu-item">Exit</button>
</div>
Вертикальное меню, в фокусе которого находится пункт «Открыть меню».

Попробуйте вживую: Шаблон меню и панели меню > Простое вертикальное меню . При использовании модификатора block для навигации по элементам используются только клавиши со стрелками вверх и вниз. Клавиши со стрелками влево и вправо свободны для действий, которые вы определяете (например, открытие подменю). Для панели меню с вложенными подменю каждый уровень представляет собой независимую группу фокуса. Попробуйте вживую: Шаблон меню и панели меню > Панель меню с всплывающими подменю

<ul role="menubar" focusgroup="menubar"
     aria-label="Application Menu" class="menubar">
    <li role="none">
        <button role="menuitem" type="button" class="menubar-item"
             aria-haspopup="menu" aria-expanded="false"
             popovertarget="filemenu">File</button>
        <ul role="menu" focusgroup="menu"
             id="filemenu" popover aria-label="File submenu" class="submenu">
            <li role="none"><button type="button" class="submenu-item"
                 autofocus>New</button></li>
            <li role="none"><button type="button" class="submenu-item">Open</button></li>
            <li role="none"><button type="button" class="submenu-item">Save</button></li>
        </ul>
    </li>
    <!-- More menu items... -->
</ul>
Выпадающее меню, в котором в фокусе находится скопированный элемент.

Попробуйте вживую: Шаблон меню и панели меню > Панель меню с всплывающими подменю . В то время как панель меню использует модификатор inline для навигации влево и вправо, подменю используют модификатор block для навигации вверх и вниз. Вложенные группы фокуса полностью независимы, поэтому они не мешают друг другу.

Радиогруппа

Пользовательская группа радиокнопок с навигацией с помощью клавиш со стрелками.

<div focusgroup="radiogroup" aria-label="Favorite color">
  <span aria-checked="false" tabindex="0">Red</span>
  <span aria-checked="false" tabindex="0">Green</span>
  <span aria-checked="true" tabindex="0" focusgroupstart >Blue</span>
  <span aria-checked="false" tabindex="0">Purple</span>
</div>
Группа радиокнопок, в фокусе которой находится синий цвет.

Попробуйте вживую: Шаблон радиогруппы > Сравнение: нативное и фокус-групповое звучание .

Хотя атрибут focusgroup обрабатывает навигацию с помощью клавиш со стрелками, вам необходимо реализовать код выбора. В этом примере JavaScript-код управляет состоянием флажка (используя атрибут aria-checked ).

Ключевые понятия

участие в фокус-группе по пунктам

Все последовательно фокусируемые потомки элемента с установленным поведением focusgroup считаются участвующими в этой focusgroup. Это означает, что элементы с отрицательным tabindex не учитываются, но элементы, изначально фокусируемые, такие как <button> учитываются, а также элементы, для которых вы указали неотрицательный tabindex .

Точка остановки

Вам не нужно управлять значениями tabindex . Даже если несколько потомков элементов по своей природе являются вкладками (например, несколько элементов <button> ), focusgroup сворачивает их в одну точку Tab. Браузер сам определяет, какой элемент является вкладкой в ​​данный момент. Попробуйте вживую: Шаблон панели инструментов > Управление tabindex не требуется .

Последняя сфокусированная память

По умолчанию, когда пользователь нажимает клавишу Tab, чтобы выйти из группы фокуса, а затем возвращается в нее, фокус возвращается к последнему элементу, на котором был сфокусирован курсор. Это критически важно для больших списков и панелей инструментов, чтобы пользователи не теряли свое местоположение. Используйте модификатор nomemory , чтобы отключить это поведение, если вы хотите, чтобы фокус всегда восстанавливался к первому элементу, или если вы используете focusgroupstart , чтобы управлять элементом, на котором изначально был сфокусирован курсор.

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

Каждое объявление focusgroup создает независимую область видимости. Вложенная focusgroup автоматически исключает стрелочную навигацию своей предшественницы. Используйте клавишу Tab для перемещения между focusgroup, а клавиши со стрелками — для навигации внутри текущей focusgroup. Попробуйте вживую: Дополнительные концепции > Вложенные focusgroup .

Поддержка теневого DOM

Focusgroup по умолчанию применяется к элементам теневого DOM. Focusgroup, объявленный на хосте теневого DOM, включает фокусируемые элементы внутри дерева теневого DOM этого хоста. Если вы хотите отказаться от этого, вы можете использовать focusgroup="none" внутри дерева теневого DOM вашего компонента.

Ключевые методы разрешения конфликтов

Некоторые элементы внутри фокус-группы, такие как <input> , <textarea> и другие элементы управления, используют клавиши со стрелками в своих целях. Когда возникает конфликт между клавишами навигации фокус-группы и поведением клавиш со стрелками у нативного элемента:

  • Клавиши со стрелками используются интерактивным элементом (например, для перемещения текстового курсора), и focusgroup этому не мешает.
  • Сочетание клавиш Tab или Shift+Tab обеспечивает механизм выхода по умолчанию, позволяя пользователю использовать навигацию по вкладке Tab для «повторного входа» в группу фокуса.

Эти механизмы экранирования применяются только при наличии фактического конфликта клавиш; оси, не вызывающие конфликта, остаются без изменений. Вы также можете вызвать preventDefault() при событии keydown , чтобы переопределить поведение клавиши со стрелками в группе фокусировки для определенных элементов. Это означает, что вы можете включать поля ввода и текстовые области в группу фокусировки, не нарушая ни одно из этих действий.

Если вы добавляете обработчики клавиш к собственным элементам, участвующим в фокус-группе, позаботьтесь о том, чтобы обеспечить аналогичный механизм экранирования, чтобы пользователи могли получить доступ к остальной части группы.

Глубокое открытие потомков

Элементы Focusgroup не обязательно должны быть прямыми дочерними элементами контейнера Focusgroup.

Браузер считает, что все последовательно фокусируемые потомки (с неотрицательным значением tabindex ) участвуют в фокус-группе, если только они не находятся внутри вложенной фокус-группы или не исключены с помощью focusgroup="none" .

<div focusgroup="toolbar" aria-label="Nested wrappers">
  <div>
    <span>
      <button type="button">Alpha</button>
    </span>
    <span>
      <button type="button">Beta</button>
    </span>
    <span>
      <button type="button">Gamma</button>
    </span>
  </div>
</div>

Навигация с помощью клавиш со стрелками работает, даже если кнопки вложены в элементы-оболочки <div> и <span> . Требований к использованию плоских списков нет, поэтому использование элементов-оболочек для стилизации допустимо.

Попробуйте вживую: Дополнительные концепции > Глубокие потомки .

Интеграция со свойством reading-flow

Как последовательная (Tab), так и направленная (клавиши со стрелками) навигация учитывают свойство CSS reading-flow если оно присутствует, следуя визуальному порядку чтения, а не порядку в исходном коде DOM.

Это гарантирует, что навигация с помощью клавиш со стрелками будет соответствовать расположению элементов на экране, которое видят пользователи.

<div focusgroup="toolbar" aria-label="Visual order"
     style="display: flex; flex-direction: row-reverse; reading-flow: flex-visual;">
  <button type="button">A (DOM first)</button>
  <button type="button">B (DOM second)</button>
  <button type="button">C (DOM third)</button>
</div>
Объект А находится в фокусе.

Хотя порядок элементов в DOM — A, B, C, визуальный порядок — C, B, A, поскольку в макете используется flex-direction: row-reverse . Однако, поскольку в коде также используется reading-flow: flex-visual , порядок чтения возвращается к A, B, C, и focusgroup соответствует этому порядку.

Нажатие клавиши Tab сначала сфокусируется на элементе C, затем нажатие клавиши Right — на элементе B, а затем на элементе A. Попробуйте вживую: Дополнительные концепции > Интеграция CSS для управления потоком чтения .

Доступность

Вывод роли ARIA

В фокус-группе токен поведения используется браузером для определения минимальной роли как для контейнера, так и для участвующих в нем элементов. Это означает, что когда атрибут focusgroup установлен для элемента с общей ролью, применяется правильная роль, основанная на выбранном поведении. Участвующие элементы элемента с общей ролью или кнопки, у которых не указана роль, будут иметь соответствующие роли, определенные автоматически. Например, следующий HTML-код:

<div focusgroup="tablist">
  <button>Tab 1</button>
  <button>Tab 2</button>
  <button>Tab 3</button>
</div>

Создаёт следующее дерево доступности, даже несмотря на то, что для кнопок не были определены роли:

+   tablist
  |
  +   tab
  |
  +   tab
  |
  +   tab

Вы всегда можете управлять поведением, задав роль напрямую.

Вопросы доступности

Проявите уважение к выбранному вами поведению при создании фокус-группы.

Использование Focusgroup должно быть максимально приближено к описанному вами поведению. Это важно для того, чтобы пользователи, использующие средства обеспечения доступности, могли перемещаться по контенту и использовать пользовательские элементы управления.

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

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

Обнаружение признаков

Чтобы начать использовать focusgroup уже сегодня, до того, как он будет полностью поддерживаться во всех браузерах, вы можете определить поддержку focusgroup в JavaScript:

if ('focusgroup' in HTMLElement.prototype) {
  // focusgroup is supported.
} else {
  // fall back to manual roving tabindex.
}

Заключение

Атрибут focusgroup проходит проверку в организациях, занимающихся стандартизацией, и мы активно разрабатываем прототип в Chromium и дорабатываем API.

Попробуйте и создайте заявку в системе отслеживания ошибок Open-UI на GitHub, создав соответствующую тему на форуме focusgroup . Нам особенно интересно ваше мнение по следующим вопросам:

  • Подходит ли интерфейс API для создаваемых вами шаблонов?
  • Возможно, мы упускаем из виду какие-то закономерности или сценарии?
  • Существуют ли элементы, для которых атрибут focusgroup не должен быть разрешен?
  • Как концепция доступности работает в ваших конкретных сценариях использования?

Спасибо за помощь в улучшении навигации по веб-страницам с помощью клавиатуры!

Узнать больше

Благодарим Мейсона Фрида, Сару Хигли, Скотта О'Хару и всё сообщество Open-UI за помощь в возвращении функции focusgroup.