Prologue
When I needed a main navigation component, I did what most developers do: I went looking for one. My requirements were simple: I wanted a navigation component that properly identified itself, that worked with both keyboard and mouse and was properly structured to support screen reader use.
I didn't find one.
So I ended up writing my own, and along the way, I thought it might be a good idea to document my progress to help educate others about what it takes to create an accessible complex widget.
I wrote the component as a demonstration to expose the process of thinking about accessibility at every step. The code base (excluding the third-party libraries I've used) can be copied and pasted into your code base, with changes made where appropriate (using your own base components and hooks, as well as your own theming and design system).
Through a series of releases and accompanying articles, I'll guide you through incorporating additional accessibility into your base components, then layering functionality and accessibility at every step until a fully accessible navigation component capable of displaying as a desktop or mobile menu is achieved.
The repository code is fully typed and tested to 100% code coverage (with the help of a few Istanbul ignore comments). Each release is tied to one or more articles and contains multiple example pages with code examples that link to the appropriately tagged release on GitHub.
Be warned, some of these articles are fairly long. This particular article marks the beginning of the process, focusing on what to look for when seeking a third-party component.
What do you look for when tasked to find a complex third-party component?
It's not uncommon for developers to hunt for existing components in third-party component libraries, the NPM library, or code in active repositories, then run examples and click around to confirm it meets the requirements for a particular need.
The challenge, of course, is that accessibility needs to be considered, but what kind of standards can be applied during an evaluation? A full audit of a component using something like Accessibility Insights might be useful, but only once a preliminary analysis indicates its potential.
The success criteria of the Web Content Accessibility Guidelines, also known as WCAG, serve as the ultimate arbiter but are deliberately written to encompass a wide range of technologies and, as I've mentioned before, are set as general guidelines rather than focusing on any one technology.
Fortunately, there are two other resources I am aware of: the ARIA Authoring Practices Guide, also known as the APG and the Web Accessibility Checklist maintained by DeQue.
The APG outlines best practices for labeling, keyboard navigation, and ARIA compliance in accessible components. However, its guidance is often based on simplified examples and should not be taken as the only way to achieve accessibility. The APG site is organized into patterns and practices, with most component information provided in patterns.
Deque's Web Accessibility Checklist can be helpful by specifying techniques for specific component types and linking them to WCAG Success Criteria or Best Practices.
I want to share how I went through the process when I needed a main navigation component while working on the revised version of the nudgestories website.
We often call a component like this a menu, and developers often adapt a menu component for this purpose. But is that really the right call? Are menus really usable for navigation? The first place I went to answer this question and gather requirements for evaluation was the APG, which lists an example of a menubar navigation pattern.
In this case, a prominent caution box appeared on the page, with the following warning.
Warnings exist for a reason, and the requirements for this pattern make it clear that the menubar adds unnecessary complexity to the main navigation component.
I then visited the recommended link for the Example Disclosure Navigation Menu and compiled a wish list of requirements to use in my evaluations.
Each pattern in the APG identifies the roles, along with the aria attributes, notes keyboard support and accessibility features. Keyboard and accessibility support are tied to the relatively simple examples, and the actual requirements must be extrapolated rather than simply accepted.
I identified my requirements using the concepts of Perceivability and Operability from WCAG, and mapped them to the corresponding success criteria, which can be viewed.
Requirements
-
Perceivable - The component should be perceivable to every person who encounters it.
- The Navigation Component should be contained in a <nav /> landmark region. (1.3.6 Identify Purpose)
- Navigation should be presented as a nested structure of unordered lists.(1.3.1 Info and Relationships)
- Lists should present with a role of "list" (inherent in an <ul /> element), items should present with a role of "listitem" (inherent in a < li /> element). (1.3.6 Identify Purpose)
The first three requirements explicitly specify the structural and semantic HTML elements required to develop the navigation component. If a component doesn't meet these foundational requirements, it's not worth my time looking any further. And while the component could be achieved with < div role="" />, native components have inherent functionality that isn't usually available when a role is simply applied to a div.
These three requirements are the easiest to look for and will allow me to identify any non-conforming components fairly easily. I'll just have to look at the rendered source code. If it meets these three requirements, I can continue the evaluation using additional requirements as listed below.
-
Perceivable - (continued)
- Buttons are associated with a sublist and indicate if the sublist is open or closed. (1.3.1 Info and Relationships)
- Any user should easily perceive the state of a list's visible or hidden state. (4.1.2 Name, Role, Value)
- A link must indicate it represents the current page through aria-current. (1.3.1 Info and Relationships / 4.1.2 Name, Role, Value)
- Buttons are associated with a sublist and indicate if the sublist is open or closed. (1.3.1 Info and Relationships)
The remaining perceivable requirements mandate that any information available to a screen user should also be identifiable through a screen reader.
-
Operable - The component should be operable to every person who encounters it.
- A sublist may be toggled to open or close when the button associated with it is pressed. (1.3.1 Info and Relationships)
- Any open dropdown closes when the entire component loses focus. (1.4.13 - Content on Hover or Focus)
- The component should allow keyboard navigation through the following keys: Enter, Escape, Space, Tab, Home, End and the arrow keys. (2.1.1 - Keyboard) (As extrapolated from APG Disclosure Navigation Keyboard Support)
The first two operable requirements don't specify how something is done; they only state that it must be done. But it's helpful to know that any interaction available through a pointing device should also be available when a keyboard is used.
Assessing Third-Party Components
When assessing third-party components, it's always a good idea to evaluate the libraries already in use for your site before adding another. In this case, the third-party component library I'm using is Adobe's react-aria-components, a primitives library containing unstyled UI components.
While there's always room for improvement, Adobe pays close attention to accessibility requirements. I've worked with several styled-component libraries before, and I like the freedom from having to fight opinionated CSS. You can read the principles Adobe used to create this library on the React Aria Quality page in their documentation.
React Aria doesn't have a specifically named navigation component, but it does have a menu component. Since "menu" and "navigation" are often used interchangeably, I'll evaluate this before moving forward.
The easiest way to begin the evaluation is to inspect the rendered HTML for an example.

Screenshot - React Aria Menu with Dev Tools open
The example menu doesn't look promising on the screen as it shows items associated with an application rather than navigation. And a peek at the source confirms that it does not conform to the HTML structure required by my evaluation. The output lacks the < nav> role and does not expose nested lists; further, the menuitem role on each div does not match my requirements for a listitem role.
When rejecting a component from a library already in use, there's bound to be pushback; someone in a standup meeting will point out that just because the example shows applications like menus, surely the code can be tweaked to display navigation. And why is it so important for the code to meet those unnecessary structural HTML requirements? After all, it does everything necessary, right?
To explain why this component is not appropriate, there needs to be an understanding of the differences between the menu/menuitem and list/listitem roles.
A menu pattern is typically used in desktop applications and their browser-based cousins (those with the role "application"). Think browser-based Google Sheets, or the online version of Microsoft Word or even Figma Design. Those require menus. A navigation component that only displays links and sublists that open when triggered by buttons doesn't need the complexity required of a menu. In fact, digging into the documentation further reveals much more complexity than is required for a navigation component; typeahead and multi-item selection are not indicated for one.
Well, my third-party component library isn't going to give me what I need, so I searched npm and GitHub for anything I could fork and came up short. I remember thinking that somewhere there must be a navigation component that does what I want, so I checked out other third-party libraries.
A library I use for common hooks, Mantine, also provides components, so I headed over to its site to take a look.
Their website does show a navbar component. The screen example doesn't meet my needs, and the rendered HTML doesn't meet my requirements.

Screenshot of the Mantine Navbar example.
Mantine actually does surround the code with a < nav / > element, but the rest of once again examining the HTML shows me their component doesn't nest buttons or links in lists or list items.
I began looking further afield, moving onto some of the better-known component libraries with a preference towards primitives:
Material-UI has attempted and abandoned several projects to create a primitives library, which makes me wary of consuming its current primitives library, Base-UI. It also lacks a navigation component, but does include a menu component.

Screenshot of Material UI Menu component example.
Examining the HTML showed many of the same issues as the Adobe React Aria component. Lack of a < nav / > surrounding the code, and while it does use an unordered list and list items, the roles are specifically set to menu and menu-item.
React Bootstrap includes a navbar component.

Screenshot of React Bootstrap NavBar detailing the lack of proper HTML structure.
The HTML structure is lacking: multiple links are wrapped in divs rather than being individually nested within list items in an unordered list.
Radix UI shows some initial promise, but the rendered HTML still doesn't conform to my structured HTML requirements.

Screenshot of Radix navigation menu.
Within the example, buttons are contained in an unordered list, which is contained in a nav element. But the failure comes when a button is activated, and the dropdown is not nested within the same list item as its parent button.
No component library I reviewed provides a navigation component that meets the first three perceivability requirements.
I have no choice but to create an accessible React navigation component that meets the criteria I've already identified. Are the requirements I've gathered enough? Probably not.
Join me next time as I begin assembling the initial requirements for creating a universally accessible main navigation component.

Top comments (0)