0

I am trying to improve my skills in developing accessible elements for my WordPress theme since the new European accessibility act will soon come into effect.

I'd like to show the HTML that my custom main menu walker generates and the javascript that goes with it, and it would be great if people could comment on whether this would pass an accessibility check.

Why am I asking this?

Online, you can find a lot of accessibility checkers, guides, and standards on what's expected from an accessible main menu navigation. Yet, the information either contradicts each other or is a bit open-ended. Thus, I have a hard time understanding if what I am doing is a step in the right direction.

Below is the HTML that is generated by my custom walker class.

/* The javascript for my menu. */
document.addEventListener('DOMContentLoaded', function() {

  //* Clicking on menu item button.
  // Get all dropdown buttons
  const dropdownButtons = document.querySelectorAll('.dropdown-button');
  // Add to each button...
  dropdownButtons.forEach(function(button) {
    // If the button iss clicked...
    button.addEventListener('click', function() {
      // Get the dropdown menu.
      const submenu = document.getElementById(button.getAttribute('aria-controls'));
      const openChildren = submenu.getElementsByClassName('sub-menu visible');

      if (openChildren) {
        for (const childMenu of openChildren) {
          childMenu.classList.remove('visible');
          childMenu.classList.add('hidden');

          childMenu.setAttribute('aria-hidden', 'true');

          const childButton = childMenu.previousElementSibling;
          childButton.setAttribute('aria-expanded', 'false');

          const buttonText = childButton.querySelector('.dropdown-button-content');
          if (buttonText) {
            buttonText.textContent = '+';
          }
        }
      }

      if (submenu.classList.contains('visible')) {
        submenu.classList.remove('visible');
        submenu.classList.add('hidden');

        submenu.setAttribute('aria-hidden', 'true');

        button.setAttribute('aria-expanded', 'false');

        const buttonText = button.querySelector(".dropdown-button-content");
        buttonText.textContent = '+';
      } else {
        submenu.classList.remove('hidden');
        submenu.classList.add('visible');

        submenu.setAttribute('aria-hidden', 'false');

        button.setAttribute('aria-expanded', 'true');

        const buttonText = button.querySelector(".dropdown-button-content");
        buttonText.textContent = '-';
      }
    });
  });

  //* If the menu item looses focus while Tab or Tab + Shift is pressed.
  document.addEventListener('focusout', function(event) {
    const focusedElement = event.target;

    // Check if the focused element is the last menu item in a submenu
    if (focusedElement.getAttribute('role') === 'menuitem') {
      const subMenu = focusedElement.closest('.sub-menu');
      if (subMenu) {
        const button = subMenu.previousElementSibling;
        // Delay the check to allow the next focused element to be determined
        setTimeout(() => {
          // Check if focus is still inside the submenu
          if (!subMenu.contains(document.activeElement)) {
            // Remove the .visible class from the submenu
            button.focus();
            button.setAttribute('aria-expanded', 'false');
            subMenu.classList.add('hidden');
            subMenu.classList.remove('visible');

            subMenu.setAttribute('aria-hidden', 'true');

            const buttonText = button.querySelector('.dropdown-button-content');
            if (buttonText) {
              buttonText.textContent = '+';
            }
          }
        }, 0);
      }
    }
  });

  //* Enable keyboard buttons.
  document.addEventListener('keydown', function(event) {
    const focusedElement = document.activeElement;

    //* Escape: Close the menu that contains focus and return focus to the element or context, e.g., menu button or parent menuitem, from which the menu was opened.
    if (event.key === 'Escape') {
      const subMenu = focusedElement.closest('.sub-menu.visible');

      // Check if the focused element is inside .sub-menu
      if (subMenu) {
        const button = subMenu.previousElementSibling;
        button.focus();
        // Close the submenu
        button.setAttribute('aria-expanded', 'false');
        subMenu.classList.add('hidden');
        subMenu.classList.remove('visible');

        subMenu.setAttribute('aria-hidden', 'true');

        const buttonText = button.querySelector('.dropdown-button-content');
        if (buttonText) {
          buttonText.textContent = '+';
        }
      }
    }

    //* Enter
    //* When focus is on a menuitem that has a submenu, opens the submenu and places focus on its first item.
    //* Otherwise, activates the item and closes the menu.
    if (event.key === 'Enter') {
      // Check if the focused element has a submenu
      if (focusedElement.classList.contains('dropdown-button')) {
        const ariaExpanded = focusedElement.getAttribute('aria-expanded');
        const ariaControls = focusedElement.getAttribute('aria-controls');
        const submenu = document.getElementById(ariaControls);

        if (submenu) {
          if (ariaExpanded === 'false') {
            // Open the submenu
            focusedElement.setAttribute('aria-expanded', 'true');
            submenu.classList.add('visible');
            submenu.classList.remove('hidden');

            submenu.setAttribute('aria-hidden', 'false');

            const buttonText = focusedElement.querySelector('.dropdown-button-content');
            if (buttonText) {
              buttonText.textContent = '-';
            }

            // Move focus to the first item in the submenu
            const firstSubmenuItem = submenu.querySelector('[role="menuitem"]');
            if (firstSubmenuItem) {
              firstSubmenuItem.focus();
            }
          } else {
            // Close the submenu
            focusedElement.setAttribute('aria-expanded', 'false');
            submenu.classList.add('hidden');
            submenu.classList.remove('visible');

            submenu.setAttribute('aria-hidden', 'true');

            const buttonText = focusedElement.querySelector('.dropdown-button-content');
            if (buttonText) {
              buttonText.textContent = '+';
            }
          }
        }
      }
    }

    //* Home: If arrow key wrapping is not supported, moves focus to the first item in the current menu or menubar.
    //* End: If arrow key wrapping is not supported, moves focus to the last item in the current menu or menubar.
    if (focusedElement.closest('.sub-menu')) {
      const menuItems = Array.from(focusedElement.closest('.sub-menu').querySelectorAll('[role="menuitem"]'));

      if (event.key === 'Home') {
        event.preventDefault();
        menuItems[0].focus();
      }

      if (event.key === 'End') {
        event.preventDefault();
        menuItems[menuItems.length - 1].focus();
      }
    }
  });
});
<nav id="main-menu" class="main-menu" aria-label="Main Navigation">
  <section class="menu-toggle-section">
    <button id="menu-toggle" class="menu-toggle" aria-label="Button for showing or hiding the Main Menu from view.">
            Menu
        </button>
  </section>
  <ul class="main-menu-list">
    <li id="menu-item-2469" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-home current-menu-item page_item page-item-2206 current_page_item menu-item-2469">
      <a href="https://darkfolklore.local/" aria-current="page" role="menuitem">Home</a>
    </li>
    <li id="menu-item-2588" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-2588">
      <a href="https://darkfolklore.local/services/" role="menuitem">Services</a>
    </li>
    <li id="menu-item-2470" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-has-children menu-item-2470">
      <a href="https://darkfolklore.local/blog/" role="menuitem">Blog</a>
      <button class="dropdown-button" aria-controls="dropdown-0" aria-haspopup="true" aria-expanded="false" aria-label="Toggle button for Blog submenu">
                <span class="dropdown-button-content" aria-hidden="true">+</span>
            </button>
      <ul class="sub-menu" role="menu" id="dropdown-0" aria-hidden="true">
        <li id="menu-item-2613" class="menu-item menu-item-type-post_type menu-item-object-post menu-item-2613">
          <a href="https://darkfolklore.local/block-image/" role="menuitem">Block: Image</a>
        </li>
        <li id="menu-item-2610" class="menu-item menu-item-type-post_type menu-item-object-post menu-item-has-children menu-item-2610">
          <a href="https://darkfolklore.local/birds-and-more-birds/" role="menuitem">Birds and More Birds</a>
          <button class="dropdown-button" aria-controls="dropdown-1" aria-haspopup="true" aria-expanded="false" aria-label="Toggle button for Birds and More Birds submenu">
                        <span class="dropdown-button-content" aria-hidden="true">+</span>
                    </button>
          <ul class="sub-menu" role="menu" id="dropdown-1" aria-hidden="true">
            <li id="menu-item-2611" class="menu-item menu-item-type-post_type menu-item-object-post menu-item-has-children menu-item-2611">
              <a href="https://darkfolklore.local/wp-6-1-font-size-scale/" role="menuitem">WP 6.1 Font size scale</a>
              <button class="dropdown-button" aria-controls="dropdown-2" aria-haspopup="true" aria-expanded="false" aria-label="Toggle button for WP 6.1 Font size scale submenu">
                                <span class="dropdown-button-content" aria-hidden="true">+</span>
                            </button>
              <ul class="sub-menu" role="menu" id="dropdown-2" aria-hidden="true">
                <li id="menu-item-2616" class="menu-item menu-item-type-post_type menu-item-object-post menu-item-2616">
                  <a href="https://darkfolklore.local/block-gallery/" role="menuitem">Block: Gallery</a>
                </li>
              </ul>
            </li>
            <li id="menu-item-2614" class="menu-item menu-item-type-post_type menu-item-object-post menu-item-2614">
              <a href="https://darkfolklore.local/block-button/" role="menuitem">Block: Button</a>
            </li>
          </ul>
        </li>
        <li id="menu-item-2615" class="menu-item menu-item-type-post_type menu-item-object-post menu-item-2615">
          <a href="https://darkfolklore.local/block-cover/" role="menuitem">Block: Cover</a>
        </li>
      </ul>
    </li>
    <li id="menu-item-2471" class="menu-item menu-item-type-post_type menu-item-object-page menu-item-2471">
      <a href="https://darkfolklore.local/contacts/" role="menuitem">Contacts</a>
    </li>
    <li class="menu-item">
      <a role="menuitem" aria-haspopup="false" aria-expanded="false" href="https://darkfolklore.local/wp-login.php?redirect_to=https%3A%2F%2Fdarkfolklore.local%2F">Sign-in</a>
    </li>
    <li class="menu-item">
      <a role="menuitem" aria-haspopup="false" aria-expanded="false" href="https://darkfolklore.local/wp-login.php?action=register">Register</a>
    </li>
  </ul>
</nav>

  1. I have used some Chrome extensions like "Accessibility Insights for Web" and "WAVE".
  2. I have used some articles from https://www.w3.org/WAI/ARIA/apg/ as a guideline.
  3. Manual testing by using TAB and other keys to test the implementation.
  4. I have not tried automated testing websites since my website is being developed locally and is not on a server.
3
  • AFAIK the new European accessibility act is only applicable to public sector bodies. If your work is for one such, there must be a server you can test on. If your work is private sector, it's not obligatory that it meets these standards, though of course it might be appreciated if it does. Still, if it's going to be online, surely you have access to a server you cans test it on? Commented Jan 22 at 9:08
  • @PSU Based on the by-laws of my country (based on this EU act), only businesses with less than 10 workers and/or yearly income of less than 2 million are excluded from implementing accessibility features in their e-commerce sites. The law references another by-law, that in turn references a standard made based on the W3C... In short, there are other exclusions. Yet, in general, it is not only for the public sector. Commented Jan 22 at 11:10
  • @PSU Testing isn't the big issue. I can get a server. The conflicting test results and different standards are the issue. Some tests return errors that another test doesn't. Some standards don't require things that another does. For example, I have read articles saying that aria-hidden must be used on dropdown menus that are not visible. Others claim that aria-hidden should be avoided and just aria-expanded or aria-has-popup is enough. So which is it? I also do not understand why there needs to be support for so many keyboard buttons like arrow keys when tab + shift can do all the navigating. Commented Jan 22 at 11:38

1 Answer 1

1

This appears to be a navigation menu. In that case, you do not want to use ARIA menu roles. Get rid of all the role="menu" and role="menuitems". ARIA menu roles are only intended to be used in very specific circumstances, primarily when you are trying to recreate a native operating system menu, which a navigation menu is not. Reference: Don't Use ARIA Menu Roles for Site Nav

Implementing a navigation menu is actually much simpler than people make it out to be. But rather than trying to re-explain the wheel, I'll just give you a link to the master: Link + Disclosure Widget Navigation. Seriously, this guy is one of the foremost experts in web accessibility and his website is consider the Accessibility Bible by many. I would follow his example very closely.

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you for the links. I will check them out. In the meantime, you seem knowledgeable on this matter. What do you think is the appropriate thing to do? Ignore the legal standards or follow them even though they ask you to implement weird features? For example, aria roles that are not needed? It kinda weirds me out. The EU government is trying to get people to make websites accessible by law and fines, yet, there is no "proper" standard on how to do that.
Are the laws really telling you that you must implement a navigation menu in a certain way, going so far as to tell you which ARIA attributes you have to use? There is nothing wrong with requiring people to create accessible sites, but I'm assuming that implies having your site tested for WCAG compliance and following best practices. Even WCAG doesn't dictate exactly how you have to implement a navigation menu.
I went through the standard again and do not see any requirement to use specific aria roles. "Even WCAG doesn't dictate exactly how you have to implement a navigation menu." Accessibility seems to be the Wild West of web development.
It appears I am just another victim of what Adian Roselli wrote about in your shared links. :D

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.