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
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}><buttonclassName={dropdown_activator}aria-haspopup="true"aria-controls={dropdownTitle}onClick={clickHandler}ref={activatorRef}>{dropdownTitle}{" "}{isOpen ? (<svgheight="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>) : (<svgheight="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><ulref={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.
.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
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
/*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
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.
- 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.
// 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);}}
useEffect Hook to detect clicks outside of the dropdown.
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
<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.
<buttonclassName={dropdown_activator}aria-haspopup="true"aria-controls={dropdownTitle}onClick={clickHandler}ref={activatorRef}>{dropdownTitle}{" "}{isOpen ? (<svgheight="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>) : (<svgheight="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.
Within the button, we render the "dropdownTitle," and depending on the "isOpen" state, we display an SVG with an arrow pointing up or down.
<ulref={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."
const items = [{slug: "/link1/",anchor: "Link 1",},{slug: "/link2/",anchor: "Link 2",},{slug: "/link3/",anchor: "Link 3",},];
Final Dropdown Menu Demonstration with ReactJS.
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