Danielle looking at the camera with a weird face and a screenshot of a portion of javascript overlaid on top.

Accessible empty top level nav items

Posted in: ,

The WordPress site editor has a really great navigation out of the box when it comes to accessibility and multi-level navigation. (We won’t be talking about mega menus here, which most sites don’t need.)

What’s a top level navigation item?

When you have a multi-level navigation, it’s important to allow someone to tab through the top level items and not have to go through every single secondary item unless they specifically want to open that subnav.

Typically, and the way the WordPress navigation block handles this, is to have the top level link with a toggle button next to it. The subnav will open on hover but wait for an intentional interaction otherwise.

The link/button method works great for most situations.

When it doesn’t work.

One thing we want to try to avoid is repetitive links. Ideally, you’d have a top level page that’s different from the pages under it. But that isn’t always the case. Sometimes there isn’t a need for that top level page, so they’ll use an empty link or a hashtag.

Or — whether true or not I can’t say — some people believe a site visitor won’t click on the top level nav item if there’s a subnav. So they’ll repeat the link maybe with different text, which is a different issue. (We want links that go to the same place to have the same text.)

Redundant links most often apply to when you have multiple links going to the same place within your content, like a blog post feed that links the image, the title, and a read more button. Sometimes it can apply to a page that has the same link a bunch of times throughout the page. Or a list of links in your content or your footer or even like we’re talking about, your navigation, that have multiple items going to the same place.

These are all different situation with different solutions. And while we may not be talking about a clear WCAG violation, we are talking about user experience and making things as pleasant to use as possible.

Start with site structure.

The best way to address repetitive links in a header nav is to avoid a multi-level navigation. Most of the time isn’t not necessary. The less you have at the top, the easier it is for people to understand the best way to navigate around your site. Put links where they make sense.

If you do decide you know what, I do want this multi level nav, then try to arrange your pages and content so that it makes sense. Group the pages with similar pages and use the top level nav item as a way to guide someone to those pages under it.

Perhaps for that second link that goes to the same page, link it to a specific section within that page so it’s got a purpose beyond “in case someone doesn’t click on the top link”. You’re at least helping someone find what they’re looking for. We’re trying to avoid not having a link as the top level item.

There are ways to hide that redundant link for screen readers, but that’s adding unnecessary markup to extra links, it skips over something that looks like it should be tab-able, and we’re always trying to avoid aria whenever we can.

Adjusting the standard solution.

If you do need to go the route of an empty top level navigation item, we have to adjust the way that’s presented in the HTML.

It’s deceiving when you’re using a mouse, because it all seems like just a link. But you need that link to go somewhere and the button to open the nav. You can’t have one element that does both.

One thing that’s lacking with the core WordPress navigation block is having a button as the top level item instead of a link. So if and until this is something that gets fixed in core, what I’ve done is written a really simple script that can be added to a theme or a code plugin to turn top level links with a hashtag into a button.

For classic themes, and maybe hybrid themes, there used to be a way to do this with a nav walker, so this is a similar solution.

It gives a consistent experience for most users and something that is still an accessible (though less ideal) experience if someone doesn’t load javascript on their site. It fails gracefully.

We’re utilizing the markup that’s already there and making some minor adjustments so we’re not messing with anything more than we have to.

The Code

// Change navigation item to button if empty with subnav
document.addEventListener('DOMContentLoaded', function () {
  const navItems = document.querySelectorAll('.wp-block-navigation__container > .wp-block-navigation-item');

  navItems.forEach(item => {
    const link = item.querySelector('a');
    const button = item.querySelector('button.wp-block-navigation__submenu-icon');

    if (!link || !button) return;

    const href = link.getAttribute('href');
    const isEmptyLink = !href || href.trim() === '#';

    if (isEmptyLink) {
      const linkText = link.textContent || link.innerText;

      // Create span with the link text
      const textSpan = document.createElement('span');
      textSpan.className = 'menu-toggle-text';
      textSpan.textContent = linkText;

      button.classList.add('sub-nav-no-link');

      // Prepend text before SVG (if any)
      const svg = button.querySelector('svg');
      if (svg) {
        button.insertBefore(textSpan, svg);
      } else {
        button.prepend(textSpan);
      }

      // Remove the now-redundant link
      link.remove();
    }
  });
});

What’s happening here?

  • Loop through our navigation items.
    • If it’s a link that has the button next to it, that means it’s a menu item that has a subnav.
    • If it’s also got a hashtag as the link, that means it’s one of our empty navigation items with a subnav.
  • Get the link text, wrap it in a span, and add a class to the button for easy styling.
  • Insert the new span-wrapped text into the button (before any icon that might be there).
  • Remove the link.

Disclaimer

It’s a fairly straightforward script, but we are using classes generated by WordPress, so this is something that could potentially need to be updated in the future if core modifies the navigation markup, so as always, test your updates if you have any concerns about compatibility and periodically check anything you’ve added custom code for.

This is something intended for the core navigation block, so a site using a different builder or solution is going to need to adjust their method.

Discover a hands-on approach to learning how to make your website more accessible.

Through one manageable task per week, you’ll not only learn about what makes a more accessible site, but be actively making your site more accessible in the process.