Juneikerc.com

How to create an dropdown (dropdown menu) with ReactJS.

Featured image of the post: How to create a dropdown with ReactJS.

In another tutorial, we already created a dropdown menu with HTML and CSS. This time, you will learn how to create a small dropdown-style menu using ReactJS and JavaScript, following the best accessibility practices, which is a common issue in most dropdown menus found on the web.

Dropdowns are often crafted solely with CSS, and a hover effect is used to reveal the list of links. In this example, we will use JavaScript to make the elements in the dropdown list appear when clicked.

JSX code for the dropdown menu

Important Note

Below, you have all the code, and further ahead, each part of this component is explained step by step.

jsx
import React, { useState, useEffect, useRef } from "react";
import {
dropdown_wrapper,
dropdown_activator,
dropdown_item_list,
active,
item_list,
} from "./dropdown.module.css";
function Dropdown({ items = [], dropdownTitle }) {
const activatorRef = useRef(null);
const dropdownListRef = useRef(null);
const [isOpen, setIsOpen] = useState(false);
const clickHandler = () => {
setIsOpen(!isOpen);
};
const keyHandler = event => {
// console.log(event);
if (event.key === "Escape" && isOpen) {
setIsOpen(false);
}
};
const clickOutsideHandler = event => {
if (dropdownListRef.current) {
if (
dropdownListRef.current.contains(event.target) ||
activatorRef.current.contains(event.target)
) {
return;
}
setIsOpen(false);
}
};
useEffect(() => {
if (isOpen) {
dropdownListRef.current.querySelector("a").focus();
document.addEventListener("mousedown", clickOutsideHandler);
} else {
document.addEventListener("mousedown", clickOutsideHandler);
}
}, [isOpen]);
return (
<div className={dropdown_wrapper} onKeyUp={keyHandler}>
<button
className={dropdown_activator}
aria-haspopup="true"
aria-controls={dropdownTitle}
onClick={clickHandler}
ref={activatorRef}
>
{dropdownTitle}{" "}
{isOpen ? (
<svg
height="24"
fill="rgb(70,70,70)"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="m0 0h24v24h-24z" fill="none" />
<path d="m7.41 15.41 4.59-4.58 4.59 4.58 1.41-1.41-6-6-6 6z" />
</svg>
) : (
<svg
height="24"
fill="rgb(70,70,70)"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="m0 0h24v24h-24z" fill="none" />
<path d="m7.41 8.59 4.59 4.58 4.59-4.58 1.41 1.41-6 6-6-6z" />
</svg>
)}
</button>
<ul
ref={dropdownListRef}
className={`${dropdown_item_list} ${isOpen ? active : ""} `}
>
{items.map((item, index) => {
return (
<li className={item_list} key={index}>
<a href={item.slug}>{item.anchor}</a>
</li>
);
})}
</ul>
</div>
);
}
export default Dropdown;

CSS required for the dropdown to function.

Important Note

This CSS code is written using CSS modules, it works the same way, but the file should have the format styles_name.module.css.

css
.dropdown_wrapper {
position: relative;
display: inline-block;
}
.dropdown_activator {
align-items: center;
background-color: inherit;
border: none;
height: 100%;
color: gray;
font-weight: 500;
letter-spacing: 1.1px;
display: flex;
align-items: center;
font-size: inherit;
padding: 1rem 0.6rem;
cursor: pointer;
width: 100%;
}
.dropdown_activator:hover {
border-bottom: 1px solid silver;
border-image: linear-gradient(
to right,
transparent 20%,
#1a1b1b,
transparent 80%
) 30;
}
.dropdown_item_list {
background: white;
display: none;
margin: 0;
z-index: 1000;
position: absolute;
box-shadow: 0 0 2px 0 gray;
border-radius: 5px;
padding: 0;
}
.dropdown_item_list.active {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
.item_list {
list-style: none;
}
.item_list:last-child a {
border-bottom: none;
}
.item_list a,
.item_list a:link {
display: flex;
/* gap: 0.8rem; */
color: #666666;
font-weight: 600;
text-decoration: none;
padding: 0.8rem;
transition: all 0.1s linear;
}
.item_list a:hover {
background-color: gray;
color: white;
}

Step-by-Step Explanation of the Dropdown Code

To create this component, we will need to use the useState, useEffect, and useRef hooks, so make sure to import them into your component.

Props required for our component

jsx
function Dropdown({ items = [], dropdownTitle }) {
/*Rest of the code*/
}

The component will receive two props:

  • items: the elements that make up the menu. For this case, we need an array of objects, with each object having slug and anchor values. Slug points to the link destination, and anchor is the link text.
  • dropdownTitle: This property will be the visible text on the button that activates the dropdown.

Hooks required for the component

jsx
/*Rest of the code*/
const activatorRef = useRef(null);
const dropdownListRef = useRef(null);
const [isOpen, setIsOpen] = useState(false);
/*Rest of the code*/
  • activatorRef and dropdownListRef: We will need to access the DOM nodes of both the button that triggers the dropdown and the list of links.
  • Also, a typical toggle state to control the behavior of the dropdown, which should be initially set to false as the dropdown will be closed initially.

Functions to Modify the Dropdown State

jsx
const clickHandler = () => {
setIsOpen(!isOpen);
};
const keyHandler = event => {
// console.log(event);
if (event.key === "Escape" && isOpen) {
setIsOpen(false);
}
};
const clickOutsideHandler = event => {
if (dropdownListRef.current) {
if (
dropdownListRef.current.contains(event.target) ||
activatorRef.current.contains(event.target)
) {
return;
}
setIsOpen(false);
}
};
  • clickHandler: This function will simply change the state from false to true and vice versa whenever the activatorRef is clicked.
  • keyHandler: This dropdown should work without using the mouse to make it usable and accessible for all users. With this function, we listen to the keyboard, and if the pressed key is Escape, we change the state to close the dropdown.
OjO

We will execute the keyHandler function in the onKeyUp event of the element that wraps the entire component.

  • clickOutsideHandler: This function will detect each time a click occurs outside of the dropdown, and if it happens, we will change the state to false. On the other hand, if the click is inside either of the two references, nothing should be done; hence the first conditional.
jsx
// Conditional to check if the dropdown reference exists.
if (dropdownListRef.current) {
// Conditional to check if the click is inside the dropdown; if it is, do nothing, and if not, change the state to false.
if (
dropdownListRef.current.contains(event.target) ||
activatorRef.current.contains(event.target)
) {
return;
}
setIsOpen(false);
}
}
Important

We will execute the clickOutsideHandler function within a useEffect because we need to detect mouse events throughout the document.

useEffect Hook to detect clicks outside of the dropdown.

jsx
useEffect(() => {
if (isOpen) {
dropdownListRef.current.querySelector("a").focus();
document.addEventListener("mousedown", clickOutsideHandler);
} else {
document.addEventListener("mousedown", clickOutsideHandler);
}
}, [isOpen]);
  • In this hook, we will run the clickOutsideHandler function across the entire document using the mousedown event.
  • If the dropdown is open, we will focus on the link inside the dropdown to continue the correct flow of clickable elements for users navigating the document with the keyboard tab key.
  • This effect should run every time the isOpen state changes, so we pass it as a dependency to the useEffect.

JSX and Rendering of the Dropdown

jsx
<div className={dropdown_wrapper} onKeyUp={keyHandler}></div>

In the container element, we pass the class "dropdown_wrapper" that we defined in our CSS, and in the onKeyUp event, we provide the keyHandler function.

jsx
<button
className={dropdown_activator}
aria-haspopup="true"
aria-controls={dropdownTitle}
onClick={clickHandler}
ref={activatorRef}
>
{dropdownTitle}{" "}
{isOpen ? (
<svg
height="24"
fill="rgb(70,70,70)"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="m0 0h24v24h-24z" fill="none" />
<path d="m7.41 15.41 4.59-4.58 4.59 4.58 1.41-1.41-6-6-6 6z" />
</svg>
) : (
<svg
height="24"
fill="rgb(70,70,70)"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="m0 0h24v24h-24z" fill="none" />
<path d="m7.41 8.59 4.59 4.58 4.59-4.58 1.41 1.41-6 6-6-6z" />
</svg>
)}
</button>

To the button that will act as an activator, we pass the class "dropdown_activator," execute the clickHandler function in the onClick event, and provide the activatorRef reference previously defined with the useRef hook.

Important Note

We set the "aria-haspopup" attribute to true to indicate that it is an element that will appear interactively, and we pass the "aria-controls" attribute the "dropdownTitle" prop to identify it.

Within the button, we render the "dropdownTitle," and depending on the "isOpen" state, we display an SVG with an arrow pointing up or down.

jsx
<ul
ref={dropdownListRef}
className={`${dropdown_item_list} ${isOpen ? active : ""} `}
>
{items.map((item, index) => {
return (
<li className={item_list} key={index}>
<a href={item.slug}>{item.anchor}</a>
</li>
);
})}
</ul>

This element will render our list of links, so we pass the previously defined "dropdownListRef" reference.

We add the class "dropdown_item_list" to apply some styles, and we also use a ternary operator to add the "active" class depending on whether the "isOpen" state is true or false. The "active" class is responsible for displaying or hiding the list.

Finally, we iterate through the "items" array and render an "li" with a link where the "href" attribute is "item.slug," and the link text is "item.anchor."

js
const items = [
{
slug: "/link1/",
anchor: "Link 1",
},
{
slug: "/link2/",
anchor: "Link 2",
},
{
slug: "/link3/",
anchor: "Link 3",
},
];
About the CSS functionality

I'm not going to explain the CSS code step by step in this example; I believe it's straightforward to understand, and I'm confident you can do it even better than I can. I hope I've been able to help. Thank you very much for visiting my website.

Final Dropdown Menu Demonstration with ReactJS.

Juneiker Castillo freelance web developer

I am Juneiker Castillo, a passionate front-end web developer deeply in love with programming and creating fast, scalable, and modern websites—a JavaScript enthusiast and a React.js lover ⚛️.

About me