Тег menu (от английского menu, «меню») записывает список команд, которые пользователь выполняет: копировать, вставить, печатать. Это парный тег, оба тега обязательны, а внутри записывают пункты li. Браузер выводит menu так же, как ul: с маркерами и отступом слева 40px. Разница не во внешнем виде, а в смысле: menu это панель команд, а не список для чтения и не меню сайта.
Обычный маркированный список разобран на странице про тег ul, а ссылки на разделы сайта по правилам записывают в тег nav. Здесь только то, что относится к menu: где он допустим, что записать внутри, как сделать из него панель кнопок и выпадающее меню.
<menu>
<li><button type="button">Копировать</button></li>
<li><button type="button">Вставить</button></li>
</menu>
В этой статье
- Тег menu в HTML: что это и как браузер его выводит
- Тег menu или ul: в чём разница и что общего
- Тег menu не для меню сайта: nav, ul и ссылки
- Где записать тег menu: в li, nav, dd и почему не в p
- Что допустимо внутри тега menu: только li, script и template
- Что записать в li внутри menu: button, a, label, select
- Панель кнопок на теге menu: flex, маркеры и разделители
- Роли тега menu: toolbar, aria-label и стрелки клавиатуры
- Кнопки-переключатели в теге menu: aria-pressed
- Выпадающее меню на теге menu и атрибуте popover
- Контекстное меню на теге menu: устаревшие атрибуты и замена
- Тег menu в JavaScript: собрать команды из массива
- Атрибуты тега menu: глобальные, role и устаревшие
- Вопросы про тег menu: подменю в li и отключённые команды
- Частые ошибки с тегом menu
Тег menu в HTML: что это и как браузер его выводит
Тег menu блочный: display: block, внешние отступы сверху и снизу по 1em, отступ слева 40px, маркер disc, у вложенного menu маркер circle. Пустой menu проходит проверку и занимает 0px высоты. Других стилей у него нет, вид панели или меню задают в 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.
ul,
menu {
list-style: none;
padding: 0;
}
Пример: сравнение стилей menu и ul
Выберите правило: вверху код, под результатом сколько свойств menu и ul совпало.
Тег menu не для меню сайта: nav, ul и ссылки
Меню сайта, то есть ссылки на страницы, записывают иначе: блок навигации nav, внутри список ul, в пунктах ссылки a. Запись nav и menu проходит проверку и выглядит так же, но menu описывает команды, которые выполняет страница, а не переходы между страницами. Без nav блок навигации никак не отмечен, а ссылки или кнопки прямо в menu без li проверка отклоняет.
<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.
<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.
<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 не допускается совсем.
<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.
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.
<menu role="toolbar" aria-label="Правка">
<li><button tabindex="0">Копия</button></li>
<li><button tabindex="-1">Вставка</button></li>
</menu>
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"]. Так вид кнопки и состояние для программ чтения берутся из одного атрибута.
btn.onclick = () => {
const on = btn.getAttribute('aria-pressed') !== 'true';
btn.setAttribute('aria-pressed', on);
};
button[aria-pressed="true"] {
background: #e6f3f1;
font-weight: 600;
}
Пример: кнопки Ж, К и Ч в menu
Нажимайте кнопки: под результатом какие из них нажаты и какое начертание получил образец.
Выпадающее меню на теге menu и атрибуте popover
Выпадающее меню собирается без скрипта на атрибуте popover: кнопка с popovertarget и menu с popover. Браузер сам открывает и закрывает список, а закрывает его по клавише Esc и по щелчку снаружи. По умолчанию такой menu стоит по центру окна и имеет рамку. Чтобы раскрыть его под кнопкой, сбрасывают margin и inset, а координаты задают скриптом в событии toggle.
<button popovertarget="m">Правка</button>
<menu id="m" popover>
<li><button>Копировать</button></li>
<li><button>Вставить</button></li>
</menu>
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 у курсора.
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.
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. Второй селектор нужен обязательно: без него подменю не открывается с клавиатуры.
li > menu {
display: none;
}
li:hover > menu,
li:focus-within > menu {
display: block;
}
Пример: подменю у кнопки «Файл»
Выберите правило: под результатом вычисленный display подменю. Наведите курсор или нажмите «Файл».
Чем отключить команду: disabled или aria-disabled?
Атрибут disabled делает кнопку неактивной и убирает её из порядка Tab: пользователь, который идёт по Tab, пропустит такую команду. Атрибут aria-disabled="true" оставляет кнопку в порядке Tab и сообщает, что команда недоступна, но клик скрипт должен игнорировать сам. Для панели, где важно показать все команды, подходит второй вариант.
<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 и игнорировать клик |