DEV Community

loading...
Cover image for Fully accessible Menu components by headless UI tailwindcss

Fully accessible Menu components by headless UI tailwindcss

Nirazan Basnet
Exploring the new tools and techniques on frontend development. Loves to meet up with new people and participate in the community. I do interesting stuff on codepen https://codepen.io/nirazanbasnet
ใƒป4 min read

This blog focuses on best-practice guidance on implementing accessibility in the menu components by using Headless UI from the creator of tailwind Labs using tailwindcss.

Why accessibility is important?

As in 2021, the Web should be accessible to information and interaction for many people. And also to provide equal access and equal opportunity to people with disabilities where there are barriers to print, audio, and visual media can be much more easily overcome through Web technologies. Also, this can help to deliver the best search results in Google and rank it according to its usability and it will boost your UX and your product.

We will be using,

Headless UI

  • Creators of Tailwind CSS, Tailwind UI, and Refactoring UI.

Tailwindcss

  • Rapidly build modern websites without ever leaving your HTML through different utility classes.

HeadlessUI Dev is a set of completely unstyled, fully accessible UI components, designed to integrate beautifully with Tailwind CSS.

Headless UI


Let's build Menu Components from headless UI

Menus are used for navigation and to provide functionality which are critical parts of web page operability.

Headless UI Menu


Using For ReactJs

# npm
npm install @headlessui/react

# Yarn
yarn add @headlessui/react
Enter fullscreen mode Exit fullscreen mode

Basic Example

Menu Buttons are built using the Menu, Menu.Button, Menu.Items, and Menu.Item components.

The Menu.Button will automatically open/close the Menu.Items when clicked, and when the menu is open, the list of items receives focus and is automatically navigable via the keyboard.

import { Menu } from "@headlessui/react";

function MyDropdown() {
  return (
    <Menu>
      {/* Render no wrapper, instead pass in a button manually. */}
      <Menu.Button as={React.Fragment}>
        <button>More</button>
      </Menu.Button>
      <Menu.Items>
        <Menu.Item>
          {({ active }) => (
            <a
              className={`${active && "bg-blue-500"}`}
              href="/account-settings"
            >
              Account settings
            </a>
          )}
        </Menu.Item>
        {/* ... */}
      </Menu.Items>
    </Menu>
  );
}
Enter fullscreen mode Exit fullscreen mode

The above code is an unstyled component by using headlessUI.

What is interesting here is if we see the generated HTML version below we can see accessibility labels and by using tailwindcss there are inline classes to make it a beautiful UI.

<div class="w-56 text-right fixed top-16">
  <div class="relative inline-block text-left">
    <div>
      <button class="inline-flex justify-center w-full px-4 py-2 text-sm font-medium text-white bg-black rounded-md bg-opacity-20 hover:bg-opacity-30 focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75" id="headlessui-menu-button-1" type="button" aria-haspopup="true"> 
         Options <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5 ml-2 -mr-1 text-violet-200 hover:text-violet-100" aria-hidden="true"><path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"></path></svg>
      </button>
    </div>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

We can see in the button tag there is

type="button" aria-haspopup="true
Enter fullscreen mode Exit fullscreen mode

Also, in the dropdown content

<div class="absolute right-0 w-56 mt-2 origin-top-right bg-white divide-y divide-gray-100 rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
     aria-labelledby="headlessui-menu-button-1" id="headlessui-menu-items-10" role="menu" tabindex="0">
    <div class="px-1 py-1 " role="none">
        <button class="text-gray-900 group flex rounded-md items-center w-full px-2 py-2 text-sm" id="headlessui-menu-item-11" role="menuitem"
                tabindex="-1">
            <svg class="w-5 h-5 mr-2" aria-hidden="true" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path d="M4 13V16H7L16 7L13 4L4 13Z" fill="#EDE9FE" stroke="#A78BFA" stroke-width="2"></path>
            </svg>
            Edit
        </button>
        <button class="text-gray-900 group flex rounded-md items-center w-full px-2 py-2 text-sm" id="headlessui-menu-item-12" role="menuitem"
                tabindex="-1">
            <svg class="w-5 h-5 mr-2" aria-hidden="true" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
                <path d="M4 4H12V12H4V4Z" fill="#EDE9FE" stroke="#A78BFA" stroke-width="2"></path>
                <path d="M8 8H16V16H8V8Z" fill="#EDE9FE" stroke="#A78BFA" stroke-width="2"></path>
            </svg>
            Duplicate
        </button>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Breaking down the code:

In the dropdown content block we have,

<div class="..." aria-labelledby="headlessui-menu-button-1" id="headlessui-menu-items-10" role="menu" tabindex="0">...</div>
Enter fullscreen mode Exit fullscreen mode

And its children div structure has

<div class="..." role="none">
        <button class="..." id="headlessui-menu-item-11" role="menuitem" tabindex="-1">...</button>
</div>

Enter fullscreen mode Exit fullscreen mode

Final Accessibility notes

1. Focus management
Clicking the Menu.Button toggles the menu and focuses on the Menu.Items component. Focus is trapped within the open menu until Escape is pressed or the user clicks outside the menu. Closing the menu returns focus to the Menu.Button.

2. Mouse interaction
Clicking a Menu.Button toggles the menu. Clicking anywhere outside of an open menu will close that menu.

3. Keyboard interaction
Keyboard Interaction

4. Other
All relevant ARIA attributes are automatically managed.
For a full reference on all accessibility features implemented in Menu, see the ARIA spec on Menu Buttons.


Reference

Please check out https://headlessui.dev/react/menu for more details as all the information and the example of the code are explained better in the docs.


Conclusion

๐Ÿ‘๐Ÿ‘ Definitely, https://headlessui.dev/ is my first choice for any React or VueJs project. I suggest you give it a try on your project and enjoy it!.

I have been writing tailwindcss classes from the beginning of their development. And I am amazed at how this framework has evolved. Many Thanks to Adam Wathan and Steve Schoger for developing the awesome products. Make sure you guys follow them.

Feel free to share your thoughts and opinions and leave me a comment if you have any problems or questions.

Till then,
Keep on Hacking, Cheers

Discussion (8)

Collapse
inhuofficial profile image
InHuOfficial • Edited

Preword: this comment is not a reflection on your article, it is really well written and thought out, this is just a warning on believing things are accessible when they may not be!

"Fully Accessible"....the accessibility of this is questionable at best.

Why aren't they using an <ul> and <li> for the list of buttons so that screen readers that don't support role="menu" still get a count of options?

Why are they using role="none" on a div, when it has no role in the first place.

Why do they stop you tabbing out of the menu when it is open, that is not expected behaviour?

You should be able to cycle through all items that start with a letter (so if you press d it should go to "duplicate", pressing d again should go to "delete"), which it does not do, it stops at the first item (which can be very confusing).

Similarly if you are on the first menu item pressing up arrow should go to the last menu item. If the focus is on the last menu item pressing down arrow should move to the first menu item.

Anyway - there are probably other issues, by the time I found all of the above I had seen enough!

Oh and WAI-ARIA is your last resort, support is not as great as you may think, even for basic WAI-ARIA attributes, which is why semantics such as <ul> are so important!

Collapse
ceoshikhar profile image
Shikhar Sharma

Funny how the author didn't respond to you.

Collapse
inhuofficial profile image
InHuOfficial • Edited

Give them chance, I only wrote it yesterday afternoon ๐Ÿ˜œ hehe.

Plus as I tried to make clear it isnโ€™t the authors fault at all, these libraries get touted as accessible, and accessibility requires a depth of knowledge that takes a long time to develop, so most developers have to believe what they are told.

I was just pointing out that most of these โ€œaccessibleโ€ libraries arenโ€™t and people should be careful!

The fact the author cares about accessibility at all is a big step in the right direction!

Collapse
youhan profile image
Alireza Jahandideh

Author is just re-presenting the docs.

Collapse
anjanarupakheti profile image
Anjana Rupakheti

Great post. Thank you for sharing!

Collapse
nirazanbasnet profile image
Nirazan Basnet Author

Thank

Collapse
suyojman profile image
Suyoj Man Tamrakar

Great Article !!

Collapse
nirazanbasnet profile image
Nirazan Basnet Author

Thanks ๐Ÿ‘๐Ÿ˜Š