Say Goodbye to Boring Dropdowns: Create Custom Dropdown Menus with Headless UI
Tags: webdev, javascript, beginners, tutorial
Published: 2023-03-19 09:55:55

Creating a website is no small feat. It requires careful planning, strategic design, and thoughtful implementation. And one of the most crucial elements of any website is its user interface (UI).

A website’s interactive UI components serve as the gateway through which users interact with the site, and they play a critical role in shaping the user experience.

From dropdown menus to modals to tabs, UI components are essential in making websites more user-friendly and intuitive. But integrating these components is no easy task since they also have to be fully accessible as well as have reusability and customization options.

And that’s where Headless UI comes to the rescue.

[Headless UI](https://www.npmjs.com/package/@headlessui/react) is an npm package that offers a practical solution to this problem. It enables you to build completely unstyled UI components that are fully accessible and easy to integrate into your website. It is created by [Tailwind Labs](https://github.com/tailwindlabs) who are also the creators of TailwindCSS.

By using Headless UI, you can focus more on styling your components with TailwindCSS and less on the functionality and accessibility side of things. This separates Headless UI from other, much more opinionated UI libraries such as Chakra and Bootstrap as the entire onus for styling is on the developer.

And based on that, we will be building a dropdown menu using Headless UI and TailwindCSS in this blog.

Let’s start.

## Setting Up the Dev Environment

When you want to use TailwindCSS inside a React app, you must first set it up and configure it.

- First, create a React app by running the command:

```
npx create-react-app my-app
```

- Next, install TailwindCSS and other dependencies like postcss and autoprefixer using the command:

```
npm install -D tailwindcss postcss autoprefixer
```

- Create config files by running the command:

```
npx tailwindcss init -p
```

Now open the tailwind.config.css file and replace the content with the provided code snippet.

```
/** @type {import('tailwindcss').Config} */

module.exports = {
content: [
   "./src/**/*.{js,jsx,ts,tsx}",
 ],
 theme: {
   extend: {},
 },
 plugins: [],
}
```

- Paste the following code snippet inside the src/index.css file:

```
@tailwind base;
@tailwind components;
@tailwind utilities;
```

You can now use TailwindCSS in your React app.

- Installing Headless UI is relatively simple; just run this command and then you can use it anywhere inside your React app.

```
npm install @headlessui/react
```

## Creating a Traditional Dropdown Menu

While creating a basic dropdown menu is a relatively straightforward task, it can become cumbersome and keep you from implementing much more important features.

```
import { useState } from 'react'

function App() {
 const [isOpen, setIsOpen] = useState(false)


 return (
   <div className="relative inline-block text-left">
     <button
       type="button"
       className="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm text-gray-700 hover:bg-gray-50"
       onClick={() => setIsOpen(!isOpen)}
     >
       More
     </button>
     {isOpen && (
       <div className="absolute z-10 mt-2 w-56 rounded-md shadow-lg bg-white ">
         <div className="py-1" role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
           <a href="/account-settings" className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900" role="menuitem">Account settings</a>
           <a href="/documentation" className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900" role="menuitem">Documentation</a>
           <span className="block px-4 py-2 text-sm text-gray-700 opacity-50" role="menuitem">Invite a friend (coming soon!)</span>
         </div>
       </div>
     )}
   </div>
 )
}

export default App
```

To create a functional dropdown menu, you have to write about 20 lines of code as shown above.

And even with that, it often lacks critical functionalities in terms of accessibility and user experience. For example, you may not be able to click outside the panel to close the dropdown or navigate using keyboard keys.

For that, you can add multiple lines of code to address these issues but it can be time-consuming and tedious. That’s where UI libraries like Headless UI can be incredibly helpful as they provide pre-built components that offer accessibility and navigation support.

## Creating a Customisable Dropdown Menu with Headless UI

When using Headless UI for a dropdown menu, it’s important to understand the basic concepts.

There are four primary components: “Menu”, “Menu.Button”, “Menu.Items”, and “Menu.Item”. As the names suggest, the “Menu” component contains everything, the “Menu.Button” is essentially a button, and the “Menu.Items” component contains multiple “Menu.Item” components.

This concept of components composition is the cornerstone of Headless UI and you will see this pattern repeat for other UI components such as [popovers](https://headlessui.com/react/popover) and [dialogs](https://headlessui.com/react/dialog).

With that said, here is the basic code based on the [official documentation](https://headlessui.com/react/menu).

```
import { Menu } from '@headlessui/react'

function App() {
  return (
    <Menu>
      <Menu.Button>More</Menu.Button>
      <Menu.Items>
        <Menu.Item>
          {({ active }) => (
            <a
              className={`${active && 'bg-blue-500'}`}
              href="/account-settings"
            >
              Account settings
            </a>
          )}
        </Menu.Item>
        <Menu.Item>
          {({ active }) => (
            <a
              className={`${active && 'bg-blue-500'}`}
              href="/account-settings"
            >
              Documentation
            </a>
          )}
        </Menu.Item>
        <Menu.Item disabled>
          <span className="opacity-75">Invite a friend (coming soon!)</span>
        </Menu.Item>
      </Menu.Items>
    </Menu>
  )
}

export default App 
```

In the code snippet, the “active” property is used as an argument to the function.

When “active” is true, the className property of the “a” element will be set to “bg-blue-500”, giving it a blue background color. Conversely, when “active” is false, the className property will be set to an empty string, removing the background color.

Also, this code provides improved functionality for instance allowing users to navigate the dropdown menu using their keyboard, closing the dropdown by clicking outside, and many more.

Now that we have a functional dropdown menu, it’s time to add some styling. Since we have installed TailwindCSS, we can use it to add style to our menu.

```
import { Menu } from '@headlessui/react'

function App() {
 return (
   <div className="shadow-md bg-white w-48">
     <Menu>
       <Menu.Button className="px-4 py-2 text-sm font-medium text-gray-700">
         More
       </Menu.Button>
       <Menu.Items className="py-2 text-sm font-medium text-gray-700">
         <Menu.Item>
           {({ active }) => (
             <a
               className={`block px-4 py-2 ${active ? 'bg-blue-500 text-white' : 'text-gray-700'
                 }`}
               href="/account-settings"
             >
               Account settings
             </a>
           )}
         </Menu.Item>
         <Menu.Item>
           {({ active }) => (
             <a
               className={`block px-4 py-2 ${active ? 'bg-blue-500 text-white' : 'text-gray-700'
                 }`}
               href="/account-settings"
             >
               Documentation
             </a>
           )}
         </Menu.Item>
         <Menu.Item disabled>
           <span className="block px-4 py-2 text-gray-400">
             Invite a friend (coming soon!)
           </span>
         </Menu.Item>
       </Menu.Items>
     </Menu>
   </div>
 )
}

export default App
```

Here is the output:

![Output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/acvarr9rwv2hm8ci95zw.jpg)

The customizability doesn’t stop here — Headless UI also allows you to further tailor the functionality of your dropdown menu. For example, you can manually control when the menu opens and closes by passing the `open` and` close` props to the relevant components.

For instance, you can check the code below which uses the `open` and` close` props.

```
import { Menu } from '@headlessui/react'

function App() {
 return (
   <Menu>
     {({ open }) => (
       <>
         <Menu.Button>More</Menu.Button>
         {open && (
           <div>
             <Menu.Items static>
               <Menu.Item>
                 {({ active }) => (
                   <a
                     className={`${active
                       ? 'bg-gray-100 text-gray-900'
                       : 'text-gray-700'
                       } block px-4 py-2 text-sm`}
                     href="/account-settings"
                   >
                     Account settings
                   </a>
                 )}
               </Menu.Item>
               <Menu.Item>
                 {({ active }) => (
                   <a
                     className={`${active
                       ? 'bg-gray-100 text-gray-900'
                       : 'text-gray-700'
                       } block px-4 py-2 text-sm`}
                     href="/documentation"
                   >
                     Documentation
                   </a>
                 )}
               </Menu.Item>
               <Menu.Item disabled>
                 <span className="block px-4 py-2 text-sm text-gray-400">
                   Invite a friend (coming soon!)
                 </span>
               </Menu.Item>
             </Menu.Items>
           </div>
         )}
       </>
     )}
   </Menu>
 )
}

export default App
```

And there are more such components API that Headless UI provides.

With these powerful features, you can create a truly bespoke dropdown menu that meets all of your unique requirements.

## Unleashing the Power of Headless UI

Speaking about integration, Headless UI can be easily integrated into React and Next.js applications. And you can seamlessly add styles to your components with TailwindCSS as well.

Another key benefit of using Headless UI is its excellent accessibility features like focus management, mouse interaction, and keyboard interaction.

![Doc](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xneouauyjuguwle9ne12.jpg)

Also, the library has taken care of all relevant [ARIA attributes](https://developer.mozilla.org/en-US/docs/web/Accessibility/ARIA/Attributes) and provided several other props like “active”, “open”, “close”, “as”, “unmount”, and many more, to help you further customize the components as per your needs.

So, whether you want to build a simple dropdown menu or a more complex UI component, Headless UI provides all the necessary functionality and accessibility features to make your development process easier and more efficient.

While Headless UI is great for building accessible UI components that work out-of-the-box and integrate seamlessly with TailwindCSS, you still need a responsive React codebase with TailwindCSS to get started.

You can generate responsive code directly from your design files in [Figma](https://figma.com/) and [Adobe XD](https://helpx.adobe.com/in/support/xd.html) using the [Locofy.ai](https://locofy.ai/) plugin.

Locofy automatically picks up your Figma auto layout settings and constraints to generate responsive code with flex properties. Additionally, you can choose to use TailwindCSS as the default styling option as well so your Locofy-generated code will utilize TailwindCSS for handling styles.

You can then use these Headless UI components to incorporate accessible drop downs, auto-complete, tabs, and other such components.

Hope you like it.

That’s it — thanks.