Создание доступных систем меню

Опубликовано: 2022-03-10
Краткий обзор ↬ В Интернете существует множество различных типов меню. Создание инклюзивного опыта — это вопрос использования правильных шаблонов меню в правильных местах, с правильной разметкой и правильным поведением.

Примечание редактора . Первоначально эта статья появилась на странице Inclusive Components. Если вы хотите узнать больше о похожих статьях об инклюзивных компонентах, подпишитесь на @inclusicomps в Твиттере или на RSS-канал. Поддерживая inclusive-components.design на Patreon, вы можете помочь сделать его самой полной базой данных надежных компонентов интерфейса.

Классификация сложная. Возьмем, к примеру, крабов. Раки-отшельники, фарфоровые крабы и мечехвосты не являются, с таксономической точки зрения, настоящими крабами. Но это не мешает нам использовать суффикс «краб». Это становится еще более запутанным, когда со временем и благодаря процессу, называемому карцинизацией , ненастоящие крабы эволюционируют, чтобы больше походить на настоящих крабов. Так обстоит дело с королевскими крабами, которые, как полагают, в прошлом были крабами-отшельниками. Представьте себе размеры их раковин!

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

Термин «раскрывающийся список» является классическим примером. Многое «раскрывается» в интерфейсах, в том числе набор <option> из элемента <select> и отображаемый JavaScript список ссылок, образующих подменю навигации. То же имя; совсем разные вещи. (Конечно, некоторые люди называют это «вытягиванием», но не будем вдаваться в подробности.)

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

Еще после прыжка! Продолжить чтение ниже ↓

Начнем с викторины. Является ли блок ссылок, свисающий с панели навигации на иллюстрации, меню?

На панели навигации есть ссылка на магазин, под которой висит набор из трех дополнительных ссылок на костюмы для собак, вафельницы и волшебные сферы соответственно.
На панели навигации есть ссылка на магазин, под которой висит набор из трех дополнительных ссылок на костюмы для собак, вафельницы и волшебные сферы соответственно. (Большой превью)

Ответ - нет, не настоящее меню.

Давно принято, что схемы навигации состоят из списков ссылок. Почти столь же давнее соглашение требует, чтобы вложенная навигация предоставлялась в виде вложенных списков ссылок. Если бы я удалил CSS для компонента, показанного выше, я бы увидел что-то вроде следующего, за исключением синего цвета и шрифта Times New Roman.

С семантической точки зрения вложенные списки ссылок в этом контексте правильны. Навигационные системы на самом деле являются таблицами содержания, и именно так устроены таблицы содержания. Единственное, что действительно заставляет нас думать о «меню», — это стиль вложенных списков и способ их отображения при наведении или фокусе.

Здесь некоторые ошибаются и начинают добавлять семантику WAI-ARIA: aria-haspopup="true" , role="menu" , role="menuitem" и т. д. Для них есть место, как мы рассмотрим, но не здесь. . Вот две причины почему:

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

Относительно (2): при перемещении по области навигации с подменю можно было бы ожидать, что каждое подменю появится при наведении или фокусировании ссылки «верхнего уровня» («Магазин» на иллюстрации). Это одновременно открывает подменю и размещает собственные ссылки в порядке фокуса. С небольшой помощью JavaScript, фиксирующего события фокуса и размытия, чтобы сохранить внешний вид подменю, когда это необходимо, кто-то, использующий клавиатуру, должен иметь возможность переходить по каждой ссылке каждого уровня по очереди.

Кнопки меню со свойством aria-haspopup="true" ведут себя иначе. Они активируются по щелчку и не имеют другой цели, кроме как открыть секретное меню.

электронные книги скидки bieten wir
Слева: кнопка меню с надписью «меню» со значком стрелки вниз и состоянием aria-expanded = false. Справа: та же кнопка меню, но с открытым меню. Эта кнопка находится в состоянии aria-expanded = true. (Большой превью)

Как показано на рисунке, независимо от того, открыто это меню или закрыто, необходимо сообщить с помощью aria-expanded . Вы должны изменять это состояние только при щелчке, а не при фокусе. Пользователи обычно не ожидают явного изменения состояния при простом событии фокуса. В нашей навигационной системе состояние на самом деле не меняется; это просто трюк со стилем. С точки зрения поведения, мы можем перемещаться по навигации с помощью Tab , как если бы такого обмана с отображением/скрытием не происходило.

Проблема с подменю навигации

Навигационные подменю (или «выпадающие» для некоторых) хорошо работают с мышью или клавиатурой, но они не так хороши, когда дело доходит до прикосновения. Когда вы нажимаете ссылку «Магазин» верхнего уровня в нашем примере в первый раз, вы говорите ему открыть подменю и перейти по ссылке.

Здесь есть два возможных решения:

  1. Запретить стандартное поведение ссылок верхнего уровня ( e.preventDefault() ) и сценария в полной семантике и поведении меню WAI-ARIA.
  2. Убедитесь, что на каждой целевой странице верхнего уровня есть оглавление в качестве альтернативы подменю.

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

Примечание: какие устройства являются сенсорными?

Заманчиво подумать: «Это не самое лучшее решение, но я добавлю его только для сенсорных интерфейсов». Проблема в том, как определить, есть ли на устройстве сенсорный экран?

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

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

Резолюция (2) является более всеобъемлющей и надежной, поскольку она обеспечивает «запасной вариант» для пользователей всех входных данных. Но пугающие кавычки вокруг запасного термина здесь вполне преднамеренны, потому что я действительно думаю, что таблицы содержимого на странице — лучший способ обеспечения навигации.

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

Таблицы содержания Gov.uk минимальны с дефисами в качестве стилей списка. В Википедии есть серая рамка с пронумерованными элементами. Оба имеют маркировку содержимого.
Таблицы содержания Gov.uk минимальны с дефисами в качестве стилей списка. В Википедии есть серая рамка с пронумерованными элементами. Оба имеют маркировку содержимого.

Таблицы содержания

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

 <nav aria-labelledby="sections-heading"> <h2>Products</h2> <ul> <li><a href="/products/dog-costumes">Dog costumes</a></li> <li><a href="/products/waffle-irons">Waffle irons</a></li> <li><a href="/products/magical-orbs">Magical orbs</a></li> </ul> </nav> <!-- each section, in order, here -->

Примечания

  • В этом примере мы представляем, что каждый раздел представляет собой отдельную страницу, как это было бы в раскрывающемся подменю.
  • Важно, чтобы каждая из этих страниц «Магазин» имела одинаковую структуру, а оглавление «Продукты» находилось в одном и том же месте. Последовательность поддерживает понимание.
  • Список группирует элементы и перечисляет их в выходных данных вспомогательных технологий, таких как синтетический голос программы чтения с экрана.
  • <nav> рекурсивно помечается заголовком с помощью aria-labelledby . Это означает, что «навигация по продуктам» будет объявляться в большинстве программ чтения с экрана при входе в регион с помощью Tab . Это также означает, что «навигация по продуктам» будет детализирована в интерфейсах элементов чтения с экрана, из которых пользователи смогут напрямую переходить к регионам.

Все на одной странице

Если вы можете разместить все разделы на одной странице, не становясь слишком длинной и трудной для прокрутки, это даже лучше. Просто дайте ссылку на хеш-идентификатор каждого раздела. Например, href="#waffle-irons" должен указывать на .

 <nav aria-labelledby="sections-heading"> <h2>Products</h2> <ul> <li><a href="#dog-costumes">Dog costumes</a></li> <li><a href="#waffle-irons">Waffle irons</a></li> <li><a href="#magical-orbs">Magical orbs</a></li> </ul> </nav> <!-- dog costumes section here --> <section tabindex="-1"> <h2>Waffle Irons</h2> </section> <!-- magical orbs section here -->

( Примечание. Некоторые браузеры плохо передают фокус связанным фрагментам страницы. Установка tabindex="-1" на целевом фрагменте исправляет это.)

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

Некоторые сайты, в том числе gov.uk Государственной цифровой службы, содержат индексные (или «тематические») страницы, которые представляют собой просто таблицы содержания. Это настолько мощная концепция, что популярный генератор статических сайтов Hugo генерирует такие страницы по умолчанию.

Диаграмма в стиле семейного древа с тематической целевой страницей вверху с двумя ответвлениями отдельных страниц. Каждое из отдельных ответвлений страницы имеет несколько ответвлений раздела страницы.
Диаграмма в стиле семейного древа с тематической целевой страницей вверху с двумя ответвлениями отдельных страниц. Каждая из отдельных ответвлений страницы имеет несколько ответвлений раздела страницы (большой предварительный просмотр)

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

Кнопки меню навигации

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

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

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

  1. Идентифицировать себя как кнопку, а не как ссылку;
  2. Определите развернутое или свернутое состояние соответствующего меню (которое, строго говоря, представляет собой просто список ссылок).

Прогрессивное улучшение

Но не будем забегать вперед. Мы должны помнить о прогрессивном улучшении и думать, как это будет работать без JavaScript.

В нерасширенном HTML-документе с кнопками мало что можно сделать (кроме кнопок отправки, но это даже близко не связано с тем, чего мы хотим достичь здесь). Вместо этого, возможно, нам следует начать просто со ссылки, которая приведет нас к навигации?

 <a href="#navigation">navigation</a> <!-- some content here perhaps --> <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/content">Content</a></li> </ul> </nav>

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

 <nav> <ul> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/content">Content</a></li> </ul> </nav>

Вы улучшаете это, добавляя кнопку в исходное состояние и скрывая навигацию (используя атрибут hidden ):

 <nav> <button aria-expanded="false">Menu</button> <ul hidden> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/contact">Contact</a></li> </ul> </nav>

Некоторые старые браузеры — вы знаете, какие — не поддерживают hidden , поэтому не забудьте добавить следующее в свой CSS. Это устраняет проблему, потому что display: none имеет такой же эффект, как скрытие меню от вспомогательных технологий и удаление ссылок из порядка фокуса.

 [hidden] { display: none; }

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

Размещение

Многие ошибаются, размещая кнопку за пределами региона. Это означало бы, что пользователи программ чтения с экрана, которые переходят к <nav> с помощью ярлыка, обнаружат, что он пуст, что не очень полезно. Со списком, скрытым от программ чтения с экрана, они просто столкнутся с этим:

 <nav> </nav>

Вот как мы можем переключать состояние:

 var navButton = document.querySelector('nav button'); navButton.addEventListener('click', function() { let expanded = this.getAttribute('aria-expanded') === 'true' || false; this.setAttribute('aria-expanded', !expanded); let menu = this.nextElementSibling; menu.hidden = !menu.hidden; });

ария-контроли

Как я писал в Aria-controls Is Poop, атрибут aria-controls , призванный помочь пользователям программ чтения с экрана переходить от управляющего элемента к контролируемому элементу, поддерживается только программой чтения с экрана JAWS. Так что на него просто нельзя положиться.

Не имея хорошего метода направления пользователей между элементами, вы должны вместо этого убедиться, что выполняется одно из следующих условий:

  1. Первая ссылка расширенного списка находится следующей в порядке фокуса после кнопки (как в предыдущем примере кода).
  2. Первая ссылка программно ориентирована на открытие списка.

В этом случае я бы рекомендовал (1). Это намного проще, так как вам не нужно беспокоиться о перемещении фокуса обратно на кнопку и о том, какое событие (я) это сделать. Кроме того, в настоящее время нет ничего, что могло бы предупредить пользователей о том, что их внимание будет перенесено на что-то другое. В истинных меню, которые мы вскоре обсудим, это работа aria-haspopup="true" .

Использование aria-controls на самом деле не приносит большого вреда, за исключением того, что делает чтение в программах чтения с экрана более подробным. Однако некоторые пользователи JAWS могут этого ожидать. Вот как это будет применяться, используя id списка в качестве шифра:

 <nav> <button aria-expanded="false" aria-controls="menu-list">Menu</button> <ul hidden> <li><a href="/">Home</a></li> <li><a href="/about">About</a></li> <li><a href="/shop">Shop</a></li> <li><a href="/contact">Contact</a></li> </ul> </nav>

Меню и роли пунктов меню

Настоящее меню (в смысле WAI-ARIA) должно идентифицировать себя как таковое, используя роль menu (для контейнера) и, как правило, menuitem (могут применяться другие дочерние роли). Эти родительские и дочерние роли работают вместе, чтобы предоставлять информацию вспомогательным технологиям. Вот как список может быть дополнен семантикой меню:

 <ul role="menu"> <li role="menuitem">Item 1</li> <li role="menuitem">Item 2</li> <li role="menuitem">Item 3</li> </ul>

Поскольку наше навигационное меню начинает вести себя как «настоящее» меню, не должны ли они присутствовать?

Короткий ответ - нет. Длинный ответ: нет, потому что наши элементы списка содержат ссылки, а элементы menuitem не предназначены для интерактивных потомков. То есть они являются элементами управления в меню.

Мы могли бы, конечно, подавить семантику списка <li> с помощью role="presentation" или role="none" (которые эквивалентны) и поместить роль menuitem на каждую ссылку. Однако это подавило бы роль неявной ссылки. Другими словами, следующий пример будет объявлен как «Главная, пункт меню», а не «Главная, ссылка» или «Главная, пункт меню, ссылка». Роли ARIA просто переопределяют роли HTML.

 <!-- will be read as "Home, menu item" --> <li role="presentation"> <a href="/" role="menuitem">Home</a> </li>

Мы хотим, чтобы пользователь знал, что он использует ссылку, и может ожидать поведения ссылки, так что это нехорошо. Как я уже сказал, настоящие меню предназначены для (управляемого JavaScript) поведения приложения.

То, что у нас осталось, — это своего рода гибридный компонент, который не совсем является настоящим меню, но, по крайней мере, сообщает пользователям, открыт ли список ссылок, благодаря состоянию aria-expanded . Это вполне удовлетворительный шаблон для навигационных меню.

Примечание: элемент <select>

Если вы с самого начала занимались адаптивным дизайном, вы, возможно, помните шаблон, согласно которому навигация была сжата в элемент <select> для узких окон просмотра.

телефон с выбранным элементом, показывающим «дом», выбранным в верхней части окна просмотра
Телефон с выбранным элементом, показывающим «дом», выбранным в верхней части окна просмотра.

Как и в случае с кнопками-переключателями на основе флажков, которые мы обсуждали, использование нативного элемента, который ведет себя так, как задумано, без дополнительных сценариев, является хорошим выбором для повышения эффективности и — особенно на мобильных устройствах — производительности. А элементы <select> — это своего рода меню с семантикой, похожей на меню, активируемое кнопкой, которое мы вскоре создадим.

Однако, как и в случае с кнопкой переключения флажка, мы используем элемент, связанный с вводом данных, а не просто с выбором. Это, вероятно, вызовет путаницу у многих пользователей, особенно потому, что этот шаблон использует JavaScript, чтобы выбранный <option> вел себя как ссылка. Неожиданное изменение контекста, вызванное этим, считается сбоем в соответствии с критерием 3.2.2 WCAG «На входе (уровень A)».

Истинные меню

Теперь, когда мы обсудили ложные меню и квази-меню, пришло время создать настоящее меню, открываемое и закрывающееся кнопкой истинного меню. С этого момента я буду называть кнопку и меню вместе просто «кнопкой меню».

Но в каких отношениях наша кнопка меню будет истинной? Что ж, это будет компонент меню, предназначенный для выбора опций в рассматриваемом приложении, реализующий всю ожидаемую семантику и соответствующее поведение, которое считается обычным для такого инструмента.

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

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

 <button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">&#x25be;</span> </button> <div role="menu"> <button role="menuitem">Easy</button> <button role="menuitem">Medium</button> <button role="menuitem">Incredibly Hard</button> </div>

Примечания

  • Свойство aria-haspopup просто указывает, что кнопка скрывает меню. Он действует как предупреждение о том, что при нажатии пользователь будет перемещен во всплывающее меню (мы вскоре рассмотрим поведение фокуса). Его значение не меняется — оно остается true во все времена.
  • <span> внутри кнопки содержит точку юникода для черного маленького треугольника, указывающего вниз. Это соглашение визуально указывает на то, что aria-haspopup делает невизуально — что нажатие кнопки покажет что-то под ней. Атрибуция aria-hidden="true" не позволяет программам чтения с экрана объявлять "треугольник, указывающий вниз" или что-то подобное. Благодаря aria-haspopup он не нужен в невизуальном контексте.
  • Свойство aria-haspopup дополняется свойством aria-expanded . Это сообщает пользователю, находится ли меню в настоящее время в открытом (развернутом) или закрытом (свернутом) состоянии путем переключения между значениями true и false .
  • Само меню берет на себя (метко названную) роль menu . Он принимает потомков с ролью menuitem . Они не обязательно должны быть прямыми дочерними элементами элемента menu , но в данном случае — для простоты.

Клавиатура и поведение фокуса

Когда дело доходит до интерактивных элементов управления, доступных с клавиатуры, лучшее, что вы можете сделать, — это использовать правильные элементы. Поскольку здесь мы используем элементы <button> , мы можем быть уверены, что события щелчка будут срабатывать при нажатии клавиш Enter и пробела, как указано в интерфейсе HTMLButtonElement . Это также означает, что мы можем отключить элементы меню, используя свойство disabled , связанное с кнопкой.

Тем не менее, взаимодействие кнопок меню с клавиатурой намного шире. Вот краткое изложение всего поведения фокуса и клавиатуры, которое мы собираемся реализовать, на основе WAI-ARIA Authoring Practices 1.1:

Введите , пробел или на кнопке меню Открывает меню
в пункте меню Перемещает фокус на следующий пункт меню или на первый пункт меню, если вы находитесь на последнем
в пункте меню Перемещает фокус на предыдущий пункт меню или на последний пункт меню, если вы находитесь на первом
на кнопку меню Закрывает меню, если открыто
Esc на пункте меню Закрывает меню и фокусирует кнопку меню

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

Применение tabindex="-1" делает элементы меню недоступными для фокуса с помощью Tab , но сохраняет возможность фокусировать элементы программно при захвате нажатий клавиш на клавишах со стрелками.

 <button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">&#x25be;</span> </button> <div role="menu"> <button role="menuitem" tabindex="-1">Easy</button> <button role="menuitem" tabindex="-1">Medium</button> <button role="menuitem" tabindex="-1">Incredibly Hard</button> </div>

Открытый метод

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

Например, метод open должен переключить значение aria-expanded на «true», изменить скрытое свойство меню на false и сфокусировать первый menuitem меню в меню, который не отключен:

 MenuButton.prototype.open = function () { this.button.setAttribute('aria-expanded', true); this.menu.hidden = false; this.menu.querySelector(':not(\[disabled])').focus(); return this; }

Мы можем выполнить этот метод, когда пользователь нажимает клавишу вниз на сфокусированном экземпляре кнопки меню:

 this.button.addEventListener('keydown', function (e) { if (e.keyCode === 40) { this.open(); } }.bind(this));

Кроме того, разработчик, использующий этот скрипт, теперь сможет открывать меню программно:

 exampleMenuButton = new MenuButton(document.querySelector('\[aria-haspopup]')); exampleMenuButton.open();

Примечание: взлом флажка

Насколько это возможно, лучше не использовать JavaScript без необходимости. Привлечение третьей технологии поверх HTML и CSS обязательно увеличивает системную сложность и хрупкость. Однако не все компоненты могут быть удовлетворительно созданы без участия JavaScript.

В случае с кнопками меню стремление заставить их «работать без JavaScript» привело к тому, что называется взломом флажков. Здесь состояние отмеченного (или не отмеченного) скрытого флажка используется для переключения видимости элемента меню с помощью CSS.

 /* menu closed */ [type="checkbox"] + [role="menu"] { display: none; } /* menu open */ [type="checkbox"]:checked + [role="menu"] { display: block; }

Для пользователей программ чтения с экрана роль флажка и отмеченное состояние не имеют смысла в этом контексте. Это можно частично решить, добавив к флажку role="button" .

 <input type="checkbox" role="button" aria-haspopup="true">

К сожалению, это подавляет неявную передачу проверенного состояния, лишая нас обратной связи о состоянии без JavaScript (плохой, хотя в данном контексте она была бы такой же «проверенной»).

Но подделать aria-expanded можно . Нам просто нужно поставить нашу метку с двумя интервалами, как показано ниже.

 <input type="checkbox" role="button" aria-haspopup="true" class="vh"> <label for="toggle" data-opens-menu> Difficulty <span class="vh expanded-text">expanded&lt;/span> <span class="vh collapsed-text">collapsed</span> <span aria-hidden="true">&#x25be;</span> </label>

Оба они визуально скрыты с помощью класса visually-hidden , но — в зависимости от того, в каком состоянии мы находимся — только один также скрыт для программ чтения с экрана. То есть только один имеет display: none , и это определяется существующим (но не сообщенным) проверенным состоянием:

 /* class to hide spans visually */ .vh { position: absolute !important; clip: rect(1px, 1px, 1px, 1px); padding: 0 !important; border: 0 !important; height: 1px !important; width: 1px !important; overflow: hidden; } /* reveal the correct state wording to screen readers based on state */ [type="checkbox"]:checked + label .expanded-text { display: inline; } [type="checkbox"]:checked + label .collapsed-text { display: none; } [type="checkbox"]:not(:checked) + label .expanded-text { display: none; } [type="checkbox"]:not(:checked) + label .collapsed-text { display: inline; }

Это умно и все такое, но наша кнопка меню все еще не завершена, поскольку ожидаемое поведение фокуса, которое мы обсуждали, просто не может быть реализовано без JavaScript.

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

Ради интереса, вот codePen, реализующий кнопку меню навигации без JavaScript.

См. пример кнопки меню Pen Navigation no JS от Heydon (@heydon) на CodePen.

( Примечание: только пробел открывает меню.)

Событие «выбери»

Выполнение некоторых методов должно генерировать события, чтобы мы могли настроить прослушиватели. Например, мы можем генерировать событие choose , когда пользователь щелкает пункт меню. Мы можем настроить это с помощью CustomEvent , что позволяет нам передать аргумент в свойство detail события. В этом случае аргумент («выбор») будет DOM-узлом выбранного пункта меню.

 MenuButton.prototype.choose = function (choice) { // Define the 'choose' event var chooseEvent = new CustomEvent('choose', { detail: { choice: choice } }); // Dispatch the event this.button.dispatchEvent(chooseEvent); return this; }

С этим механизмом мы можем делать все, что угодно. Возможно, у нас есть живой регион с id menuFeedback :

 <div role="alert"></div>

Теперь мы можем настроить обработчик и заполнить живую область информацией, скрытой внутри события:

 exampleMenuButton.addEventListener('choose', function (e) { // Get the node's text content (label) var choiceLabel = e.details.choice.textContent; // Get the live region node var liveRegion = document.getElementById('menuFeedback'); // Populate the live region liveRegion.textContent = 'Your difficulty level is ${choiceLabel}'; });
Когда пользователь выбирает вариант, меню закрывается, и фокус возвращается к кнопке меню. Важно, чтобы пользователи возвращались к триггерному элементу после закрытия меню.
Когда пользователь выбирает вариант, меню закрывается, и фокус возвращается к кнопке меню. Важно, чтобы пользователи возвращались к инициирующему элементу после закрытия меню. (Большой превью)

Когда пункт меню выбран, пользователь программы чтения с экрана услышит: «Вы выбрали [метка пункта меню]» . Активный регион (определенный здесь атрибутом role=“alert” ) сообщает о своем содержимом программам чтения с экрана всякий раз, когда это содержимое изменяется. Живая область не является обязательной, но это пример того, что может произойти в интерфейсе в ответ на выбор пользователя в меню.

Сохраняющийся выбор

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

Атрибут aria-checked="true" работает для элементов, которые вместо menuitem берут на себя роль menuitemradio . Расширенная разметка со вторым отмеченным элементом ( set ) выглядит следующим образом:

 <button aria-haspopup="true" aria-expanded="false"> Difficulty <span aria-hidden="true">&#x25be;</span> </button> <div role="menu"> <button role="menuitemradio" tabindex="-1">Easy</button> <button role="menuitemradio" aria-checked="true" tabindex="-1">Medium</button> <button role="menuitemradio" tabindex="-1">Incredibly Hard</button> </div>

Нативные меню на многих платформах обозначают выбранные элементы с помощью флажков. Мы можем сделать это без проблем, используя немного дополнительного CSS:

 [role="menuitem"] [aria-checked="true"]::before { content: '\2713\0020'; }

При перемещении по меню с запущенным средством чтения с экрана фокус на этом отмеченном элементе вызовет объявление, например «галочка, средний элемент меню, отмечен» .

Поведение при открытии меню с отмеченным menuitemradio немного отличается. Вместо фокуса на первом (включенном) элементе в меню фокусируется отмеченный элемент.

Кнопка меню начинается с неоткрытого меню. При открытии ориентирован второй (средний) уровень сложности. Перед ним ставится галочка, основанная на наличии атрибута aria-checked.
Кнопка меню начинается с неоткрытого меню. При открытии ориентирован второй (средний) уровень сложности. Перед ним ставится галочка, основанная на наличии атрибута aria-checked. (Большой превью)

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

Использование кнопки меню с программой чтения с экрана

В этом видео я покажу вам, как использовать кнопку меню с программой чтения с экрана Voiceover и Chrome. В примере используются элементы с menuitemradio , aria-checked и обсуждаемым поведением фокуса. Подобный опыт можно ожидать во всех популярных программах для чтения с экрана.

Инклюзивная кнопка меню на Github

Китти Жиродель и я вместе работали над созданием компонента кнопки меню с функциями API, которые я описал, и другими. Вы должны поблагодарить Хьюго за многие из этих функций, поскольку они были основаны на работе, которую они проделали над a11y-dialog — доступным модальным диалогом. Он доступен на Github и NPM.

 npm i inclusive-menu-button --save

Кроме того, Китти создала версию React для вашего удовольствия.

Контрольный список

  • Не используйте семантику меню ARIA в системах меню навигации.
  • На сайтах с большим объемом контента не прячьте структуру во вложенных раскрывающихся меню навигации.
  • Используйте aria-expanded , чтобы указать открытое/закрытое состояние меню навигации, активируемого кнопкой.
  • Убедитесь, что указанное меню навигации находится следующим в порядке фокуса после кнопки, которая открывает/закрывает его.
  • Никогда не жертвуйте удобством использования в погоне за решениями без JavaScript. Это тщеславие.