menubar

Роль для кастомной строки меню как в программе.

Время чтения: 8 мин

Кратко

Скопировано

Самостоятельная роль виджета из WAI-ARIA для строки меню, которая обычно встречается в операционных системах, программах и веб-приложениях.

В HTML нет тега с ролью menubar.

Пример

Скопировано
        
          
          <div role="menubar">  <button    role="menuitem"    type="button"    aria-expanded="false"    aria-controls="fonts"    aria-haspopup="menu"  >    Начертание  </button>  <ul    role="menu"    id="fonts"    tabindex="-1"  >    <!-- Содержимое подменю -->  </ul>  <!-- Остальные элементы строки меню --></div>
          <div role="menubar">
  <button
    role="menuitem"
    type="button"
    aria-expanded="false"
    aria-controls="fonts"
    aria-haspopup="menu"
  >
    Начертание
  </button>
  <ul
    role="menu"
    id="fonts"
    tabindex="-1"
  >
    <!-- Содержимое подменю -->
  </ul>
  <!-- Остальные элементы строки меню -->
</div>

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

Как пишется

Скопировано

Задайте любому HTML-тегу атрибут role="menubar", лучше всего <div> или <ul>.

Строка меню, как и любая другая навигация, должна содержать как минимум один элемент. Это может быть обычный пункт menuitem, дополнительно раскрывающий подменю menu, пункт в виде чекбокса menuitemcheckbox или пункт в виде радиокнопки menuitemradio.

Пункты могут располагаться отдельно или объединяться в группы с ролью group. Когда в строке несколько групп, их можно отделить друг от друга обычными (неинтерактивными) разделителями с ролью separator.

        
          
          <ul role="menubar">  <span role="group">    <li role="presentation">      <span        role="menuitemcheckbox"        aria-selected="true"        tabindex="0"      >        Показать превью      </span>    </li>    <li role="presentation">      <span        role="menuitemcheckbox"        aria-selected="false"        tabindex="-1"      >        Показать неразрывные пробелы      </span>    </li>  </span>  <span    role="separator"    aria-orientation="vertical"  >  </span>  <li role="presentation">    <span      role="menuitem"      tabindex="-1"    >      Сохранить    </span>  </li>  <!-- Остальные элементы --></ul>
          <ul role="menubar">
  <span role="group">
    <li role="presentation">
      <span
        role="menuitemcheckbox"
        aria-selected="true"
        tabindex="0"
      >
        Показать превью
      </span>
    </li>
    <li role="presentation">
      <span
        role="menuitemcheckbox"
        aria-selected="false"
        tabindex="-1"
      >
        Показать неразрывные пробелы
      </span>
    </li>
  </span>

  <span
    role="separator"
    aria-orientation="vertical"
  >
  </span>

  <li role="presentation">
    <span
      role="menuitem"
      tabindex="-1"
    >
      Сохранить
    </span>
  </li>
  <!-- Остальные элементы -->
</ul>

        
        
          
        
      

У menubar есть свойство aria-orientation со значением horizontal по умолчанию. Благодаря значению пользователи скринридеров и других вспомогательных технологий знают, что могут перемещаться по пунктам клавишами со стрелками влево и вправо .

Также можете задавать menubar все глобальные ARIA-атрибуты и несколько специальных атрибутов виджетов:

  • aria-disabled, когда все элементы строки неактивны, но на них можно сделать фокус;
  • aria-activedescendant, когда нужно рассказать о выбранном пункте из строки или связанного подменю.

У строки может быть имя — её краткое название. Если оно видно всем, используйте aria-labelledby. Когда оно доступно только скринридерам, задайте aria-label.

        
          
          <ul  role="menubar"  aria-labelledby="label">  <span id="label">Редактор кода</span>  <!-- Элементы строки меню --></ul><ul  role="menubar"  aria-label="Редактор кода">  <!-- Элементы строки меню --></ul>
          <ul
  role="menubar"
  aria-labelledby="label"
>
  <span id="label">Редактор кода</span>
  <!-- Элементы строки меню -->
</ul>

<ul
  role="menubar"
  aria-label="Редактор кода"
>
  <!-- Элементы строки меню -->
</ul>

        
        
          
        
      

Навигация с клавиатуры

Скопировано

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

На строку попадают с помощью клавиши Tab или сочетания Shift Tab. Когда оказались на ней в первый раз, фокус должен установиться на первом пункте, а в последующем — на последнем активном элементе. Когда находитесь на пункте и нажали на Tab, фокус перемещается на следующий интерактивный элемент после строки меню, на Tab Shift — на предыдущий. Если в строке открыто подменю, оно закроется.

Между пунктами горизонтальной строки перемещаются клавишами со стрелками влево и вправо . Если она вертикальная и элементы расположены друг под другом, по ним проходят стрелками вверх и вниз . Также клавиша Home должна переносить на первый пункт строки, End — на последний.

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

Когда нажали на Enter или стрелку вниз на пункте с ролью menuitem, раскрывается связанное с ним подменю и фокус устанавливается на первом пункте из него. Если в фокусе радиокнопки menuitemradio или чекбоксы menuitemcheckbox, Enter выбирает их или отменяет предыдущий выбор.

Дополнительно можете поддерживать и пробел. Он делает то же, что и Enter: раскрывает подменю или выбирает и отменяет выбор чекбокса или радиокнопки.

Не обязательно, но при фокусе на строке можно отслеживать нажатие на клавиши с буквами и символами. Пользователи смогут быстро переместиться к нужным пунктам, которые начинаются со знака с нажатой клавиши. Например, попасть на пункт «Настройки» при нажатии на клавишу H.

Управление фокусом

Скопировано

Для правильной навигации в строке меню не обойтись без HTML-атрибута tabindex. Это особенно важно, когда создаёте кастомные элементы на тегах, с которыми обычно не могут взаимодействовать пользователи. К примеру, <span> и <div>.

Только у одного пункта из menubar может быть tabindex="0" — у первого элемента до того, как на строке сделали фокус, и у текущего пункта в фокусе. Остальные пункты должны быть с tabindex="-1", пока их не выбрали. В том числе отрицательный tabindex должен быть у закрытого пока подменю.

        
          
          <ul role="menubar">  <li role="presentation">    <span      role="menuitem"      tabindex="0"    >      Прикрепить картинку    </span>  </li>  <li role="presentation">    <span      role="menuitemcheckbox"      aria-selected="false"      tabindex="-1"    >      Показать превью    </span>  </li>  <li role="presentation">    <span      role="menuitem"      aria-expanded="false"      aria-controls="color"      aria-haspopup="menu"      tabindex="-1"    >      Цвет    </span>  </li>  <ul    role="menu"    id="color"    tabindex="-1"  >    <!-- Содержимое подменю -->  </ul></ul>
          <ul role="menubar">
  <li role="presentation">
    <span
      role="menuitem"
      tabindex="0"
    >
      Прикрепить картинку
    </span>
  </li>
  <li role="presentation">
    <span
      role="menuitemcheckbox"
      aria-selected="false"
      tabindex="-1"
    >
      Показать превью
    </span>
  </li>
  <li role="presentation">
    <span
      role="menuitem"
      aria-expanded="false"
      aria-controls="color"
      aria-haspopup="menu"
      tabindex="-1"
    >
      Цвет
    </span>
  </li>
  <ul
    role="menu"
    id="color"
    tabindex="-1"
  >
    <!-- Содержимое подменю -->
  </ul>
</ul>

        
        
          
        
      

Один из многочисленных вариантов решения на JavaScript:

        
          
          const menuItems = Array.from(document.querySelectorAll('span[data-item]'))let lastFocusedItem = nulllet currentFocusedButtonIndex = -1menuItems.forEach((item, index) => {  item.addEventListener('focus', () => {    if (lastFocusedItem && lastFocusedItem !== item) {      lastFocusedItem.setAttribute('tabindex', '-1')    }    item.setAttribute('tabindex', '0')    lastFocusedItem = item  })})
          const menuItems = Array.from(document.querySelectorAll('span[data-item]'))
let lastFocusedItem = null
let currentFocusedButtonIndex = -1

menuItems.forEach((item, index) => {
  item.addEventListener('focus', () => {
    if (lastFocusedItem && lastFocusedItem !== item) {
      lastFocusedItem.setAttribute('tabindex', '-1')
    }

    item.setAttribute('tabindex', '0')
    lastFocusedItem = item
  })
})

        
        
          
        
      

Как понять

Скопировано

Обычное меню на сайтах состоит из ссылок, поэтому достаточно использовать <ul> внутри <nav>.

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

Первая часть элемента — строка меню с ролью menubar. Она состоит из нескольких пунктов с видимыми текстовыми названиями. У первого пункта развёрнуто подменю с ролью menu. Под строкой меню находится тулбар с ролью toolbar. В нём собраны команды для выбора шрифта, выравнивания текста, проверки орфографии и поиска.