Тег menu в HTML: что это и чем отличается от ul

Тег menu (от английского menu, «меню») записывает список команд, которые пользователь выполняет: копировать, вставить, печатать. Это парный тег, оба тега обязательны, а внутри записывают пункты li. Браузер выводит menu так же, как ul: с маркерами и отступом слева 40px. Разница не во внешнем виде, а в смысле: menu это панель команд, а не список для чтения и не меню сайта.

Обычный маркированный список разобран на странице про тег ul, а ссылки на разделы сайта по правилам записывают в тег nav. Здесь только то, что относится к menu: где он допустим, что записать внутри, как сделать из него панель кнопок и выпадающее меню.

HTML
<menu>
  <li><button type="button">Копировать</button></li>
  <li><button type="button">Вставить</button></li>
</menu>

Тег menu в HTML: что это и как браузер его выводит

Тег menu блочный: display: block, внешние отступы сверху и снизу по 1em, отступ слева 40px, маркер disc, у вложенного menu маркер circle. Пустой menu проходит проверку и занимает 0px высоты. Других стилей у него нет, вид панели или меню задают в CSS.

CSS браузера
menu {
  display: block;
  margin-block: 1em;
  padding-inline-start: 40px;
  list-style-type: disc;
}

Пример: menu, вложенный menu и пустой menu

Результат

Выберите запись: под результатом показано, как браузер вывел menu.

Тег menu или ul: в чём разница и что общего

В браузерах menu и ul ведут себя одинаково: те же стили, та же роль list у списка и listitem у пунктов. Различие только в смысле: по стандарту menu это панель команд (toolbar), а ul любой маркированный список. Есть и практическая ловушка: селектор ul тег menu не выбирает, поэтому сброс ul { list-style: none } на menu не действует. Сбрасывают оба тега сразу. Пункты внутри записывают так же, как в теге li.

CSS
ul,
menu {
  list-style: none;
  padding: 0;
}

Пример: сравнение стилей menu и ul

Результат

Выберите правило: вверху код, под результатом сколько свойств menu и ul совпало.

Тег menu не для меню сайта: nav, ul и ссылки

Меню сайта, то есть ссылки на страницы, записывают иначе: блок навигации nav, внутри список ul, в пунктах ссылки a. Запись nav и menu проходит проверку и выглядит так же, но menu описывает команды, которые выполняет страница, а не переходы между страницами. Без nav блок навигации никак не отмечен, а ссылки или кнопки прямо в menu без li проверка отклоняет.

HTML
<nav>
  <ul>
    <li><a href="/">Главная</a></li>
    <li><a href="/lessons">Уроки</a></li>
  </ul>
</nav>

Пример: как записать меню сайта

Результат

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

Где записать тег menu: в li, nav, dd и почему не в p

Блочный menu записывают там, где допустим блок: в div, nav, header, figure, ячейке таблицы и в описании dd. Вложенный menu записывают только внутри li. В span, ul и button такая запись неверна. В абзаце p она тоже неверна: браузер закрывает абзац перед menu, а после списка остаётся пустой p.

HTML
<ul>
  <li>Правка
    <menu>
      <li>Копировать</li>
    </menu>
  </li>
</ul>

Пример: где допустим menu

Результат

Выберите запись: вверху код, под результатом написано, допустима ли она. Для menu в p показано дерево, которое построил браузер.

Что допустимо внутри тега menu: только li, script и template

Прямыми потомками menu бывают только li, а также script и template. Текст, button, a, p, ul и другой menu прямо в menu записывать нельзя, хотя браузер выведет их без жалоб: кнопка, например, остаётся обычной строчной кнопкой. Нужное содержимое всегда оборачивают в li.

HTML
<menu>
  <li><button type="button">Копировать</button></li>
</menu>

Пример: что допустимо прямо в menu

Результат

Выберите запись: вверху код, под результатом написано, допустима ли она.

Что записать в li внутри menu: button, a, label, select

В li внутри menu записывают то, что пользователь выполняет или переключает: кнопку, ссылку, флажок с label, выпадающий select, кнопку с иконкой. У иконки обязателен alt: он становится названием команды. Атрибуты у li действуют те же, что в ul: value разрешён только в ol, а type у li не допускается совсем.

HTML
<menu>
  <li>
    <label>
      <input type="checkbox">
      Показать сетку
    </label>
  </li>
</menu>

Пример: что записать внутри li

Результат

Выберите запись: вверху код, под результатом написано, допустима ли она. Иконка в примере нарисована квадратом.

Панель кнопок на теге menu: flex, маркеры и разделители

Чтобы из menu получилась панель кнопок, ему задают display: flex. Одного flex мало: пункты встанут в ряд, но маркеры и отступ слева останутся, потому что li по-прежнему list-item. Поэтому добавляют list-style: none и padding: 0. Разделители между командами рисуют правилом li + li, а перенос на узком экране включает flex-wrap: wrap.

CSS
menu {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 0;
  list-style: none;
}

Пример: от списка к панели кнопок

Результат

Выберите правило: вверху код, под результатом вычисленные display, маркер и отступ.

Роли тега menu: toolbar, aria-label и стрелки клавиатуры

Роль toolbar у menu заменяет роль list: панель распознаётся как панель, а не как список. Название панели задаёт aria-label. Но сама роль поведения не добавляет: Tab по-прежнему останавливается на каждой кнопке. Привычное поведение панели, одна остановка Tab и стрелки между кнопками, делают скриптом: у активной кнопки tabindex="0", у остальных -1, стрелки переносят 0 и фокус. Проверка пропускает у menu роли toolbar, menu, menubar, list, listbox, tablist, group, none, а также presentation, radiogroup и tree.

HTML
<menu role="toolbar" aria-label="Правка">
  <li><button tabindex="0">Копия</button></li>
  <li><button tabindex="-1">Вставка</button></li>
</menu>
JavaScript
menu.onkeydown = e => {
  if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft') return;
  const i = btns.indexOf(document.activeElement);
  const n = e.key === 'ArrowRight' ? i + 1 : i - 1;
  if (!btns[n]) return;
  btns.forEach(b => b.tabIndex = -1);
  btns[n].tabIndex = 0;
  btns[n].focus();
};

Пример: остановки Tab и стрелки в панели

Результат

Выберите вариант, нажмите Tab и стрелки: под результатом роль, число остановок Tab и кнопка с фокусом.

Кнопки-переключатели в теге menu: aria-pressed

Кнопка, которая включает и выключает режим (жирный, курсив, сетка), получает атрибут aria-pressed: false значит выключено, true включено. Скрипт меняет значение при клике, а оформление нажатого состояния задают селектором button[aria-pressed="true"]. Так вид кнопки и состояние для программ чтения берутся из одного атрибута.

JavaScript
btn.onclick = () => {
  const on = btn.getAttribute('aria-pressed') !== 'true';
  btn.setAttribute('aria-pressed', on);
};
CSS
button[aria-pressed="true"] {
  background: #e6f3f1;
  font-weight: 600;
}

Пример: кнопки Ж, К и Ч в menu

Результат

Нажимайте кнопки: под результатом какие из них нажаты и какое начертание получил образец.

Выпадающее меню на теге menu и атрибуте popover

Выпадающее меню собирается без скрипта на атрибуте popover: кнопка с popovertarget и menu с popover. Браузер сам открывает и закрывает список, а закрывает его по клавише Esc и по щелчку снаружи. По умолчанию такой menu стоит по центру окна и имеет рамку. Чтобы раскрыть его под кнопкой, сбрасывают margin и inset, а координаты задают скриптом в событии toggle.

HTML
<button popovertarget="m">Правка</button>
<menu id="m" popover>
  <li><button>Копировать</button></li>
  <li><button>Вставить</button></li>
</menu>
CSS и JavaScript
menu[popover] {
  margin: 0;
  inset: auto;
}
m.addEventListener('toggle', e => {
  if (e.newState !== 'open') return;
  const r = btn.getBoundingClientRect();
  m.style.top = r.bottom + 4 + 'px';
  m.style.left = r.left + 'px';
});

Пример: выпадающее меню на popover

Результат

Выберите вариант и нажмите «Правка»: под результатом где открылось меню.

Контекстное меню на теге menu: устаревшие атрибуты и замена

Раньше контекстное меню задавали атрибутами: contextmenu у элемента, type и label у menu и тегом menuitem. Поддержку убрали из стандарта, а Firefox отключил её с версии 85. Сейчас contextmenu и menuitem проверка считает ошибкой, а type и label устаревшими. Замена такая: слушать событие contextmenu, отменить системное меню и открыть свой menu с popover у курсора.

JavaScript
area.addEventListener('contextmenu', e => {
  e.preventDefault();
  m.showPopover();
  const x = Math.min(e.clientX, innerWidth - m.offsetWidth);
  const y = Math.min(e.clientY, innerHeight - m.offsetHeight);
  m.style.left = x + 'px';
  m.style.top = y + 'px';
});

Пример: своё контекстное меню

Результат

Нажмите правой кнопкой мыши в рамке (на телефоне удерживайте палец): под результатом положение меню и выбранная команда.

Тег menu в JavaScript: собрать команды из массива

Когда команд много или они приходят из данных, menu собирают скриптом: для каждой команды создают li и button. Обработчик клика вешают один раз на сам menu и находят кнопку через e.target.closest('button'): так новые команды работают без повторного добавления обработчиков. Идентификатор команды хранят в data-cmd.

JavaScript
const cmds = [
  { id: 'copy', label: 'Копировать' },
  { id: 'paste', label: 'Вставить' },
];
for (const c of cmds) {
  const li = document.createElement('li');
  const b = document.createElement('button');
  b.type = 'button';
  b.dataset.cmd = c.id;
  b.textContent = c.label;
  li.append(b);
  menu.append(li);
}
menu.addEventListener('click', e => {
  const b = e.target.closest('button');
  if (b) run(b.dataset.cmd);
});

Пример: команды из массива

Результат

Добавьте команды и нажмите на них: под результатом число команд и последняя нажатая.

Атрибуты тега menu: глобальные, role и устаревшие

У menu нет собственных рабочих атрибутов. Работают глобальные, в том числе popover и role. Устаревшие type, label и compact браузеры не используют.

АтрибутСтатусЧто делаетПример записи
id, class, title, lang, tabindex, hidden, aria-labelактуаленГлобальные атрибуты: aria-label задаёт название панели, hidden скрывает весь список<menu aria-label="Правка">
popoverактуаленПревращает menu в выпадающий список, который открывает кнопка с popovertarget<menu popover>
role="toolbar", menu, menubar, list, listbox, tablist, group, none, presentation, radiogroup, treeдопустимМеняют роль списка. Поведение (стрелки, фокус) остаётся за скриптом<menu role="toolbar">
role="navigation", role="button"недопустимВалидатор считает такие роли ошибкой для menu<menu role="button">
type, label, compactустарелРаньше задавали контекстное меню и плотность. Сейчас валидатор выдаёт предупреждение<menu type="toolbar">
contextmenu у элемента, тег menuitemнедопустимУбраны из стандарта и браузеров, валидатор считает ошибкой<div contextmenu="m">
start, reversedнедопустимЭти атрибуты бывают только у ol<menu start="2">
itempropс условиемРаботает только внутри элемента с itemscope, иначе валидатор считает это ошибкой<menu itemprop="x">

Вопросы про тег menu: подменю в li и отключённые команды

Два вопроса, которых нет в разделах выше: как сделать подменю и как оформить отключённую команду.

Как сделать подменю без JavaScript?

Вложенный menu записывают внутри li рядом с кнопкой. Скрывают его правилом display: none, а показывают селекторами li:hover > menu и li:focus-within > menu. Второй селектор нужен обязательно: без него подменю не открывается с клавиатуры.

CSS
li > menu {
  display: none;
}
li:hover > menu,
li:focus-within > menu {
  display: block;
}

Пример: подменю у кнопки «Файл»

Результат

Выберите правило: под результатом вычисленный display подменю. Наведите курсор или нажмите «Файл».

Чем отключить команду: disabled или aria-disabled?

Атрибут disabled делает кнопку неактивной и убирает её из порядка Tab: пользователь, который идёт по Tab, пропустит такую команду. Атрибут aria-disabled="true" оставляет кнопку в порядке Tab и сообщает, что команда недоступна, но клик скрипт должен игнорировать сам. Для панели, где важно показать все команды, подходит второй вариант.

HTML
<li>
  <button aria-disabled="true">Вставка</button>
</li>

Пример: отключённая команда в панели

Результат

Выберите атрибут: под результатом сколько кнопок получают фокус.

Частые ошибки с тегом menu

Типичные ошибки с тегом menu и как их исправить.

ОшибкаЧто происходитКак исправить
menu для меню сайтаРазметка проходит проверку, но смысл неверен: это не командыЗаписать nav со списком ul и ссылками
button, a или текст прямо в menuОшибка валидатораОбернуть содержимое в li
menu в p, span, ul или buttonОшибка валидатора, в p остаётся пустой абзацЗаписать menu рядом или внутри li
menu или ul прямо в menuОшибка валидатораВложить в li
display: flex без list-style: noneПункты в ряд, но маркеры и отступ осталисьДобавить list-style: none и padding: 0
Сброс ul { list-style: none } для панели на menuСелектор ul тег menu не затрагиваетЗаписать ul, menu
value или type у liОшибка валидатораУбрать, а нумерацию делать через ol
start или reversed у menuОшибка валидатора: атрибуты есть только у olУбрать или заменить menu на ol
role="navigation" или role="button" у menuОшибка валидатораДля меню сайта взять nav, для панели role="toolbar"
role="toolbar" без скрипта стрелокTab останавливается на каждой кнопкеДобавить roving tabindex и стрелки
Кнопка-переключатель без aria-pressedСостояние видно только глазамиДобавить aria-pressed и менять его при клике
popover без позицииМеню открывается по центру окнаСбросить margin и inset, задать координаты скриптом
Контекстное меню через contextmenu, menuitem, type, labelОшибка или предупреждение валидатора, браузеры такое меню не показываютСлушать событие contextmenu и открывать menu с popover
Подменю только по :hoverС клавиатуры оно не открываетсяДобавить :focus-within
disabled для команды, которую нужно показатьКнопка выпадает из порядка TabИспользовать aria-disabled и игнорировать клик
Категория
Поделиться в социальных сетях

Отправить комментарий

0 Комментарии

Отправить комментарий (0)

#buttons=(Хорошо) #days=(20)

Мы используем файлы cookie для улучшения работы сайта. Подробнее
Хорошо
×