Juneikerc.com

Cómo crear un dropdown (menu desplegable) accesible con reactjs

imagen destacada del post: Cómo crear un dropdown con reactjs

En otro tutorial ya creamos menu desplegable con html y css en esta ocasión aprenderás a crear pequeño menú desplegable estilo dropdown utilizando reactjs y javascript siguiendo las mejores prácticas de accesibilidad, uno de los puntos en los que fallan la mayoría de menús desplegables que se encuentran en la web.

Los dropdowns suelen estar escritos solo con css y para hacer aparecer la lista de enlaces se usa un efecto hover para este ejemplo usaremos javascript para que los elementos de la lista desplegable aparezcan cuando se haga click sobre el.

Código jsx de menú desplegable

Nota Importante

A continuación tienes todo el código más adelante esta explicado paso a paso cada parte de este componente

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 necesario para el funcionamiento del dropdown

Nota importante

Este código css esta escrito usando css modules funciona igual pero el archivo debe tener el formato nombre_de_estilos.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;
}

Explicación paso a paso del código del dropdown

Para crear este componente necesitaremos usar los ganchos useState, useEffect y useRef así que asegurate de importarlos en tu componente.

Props necesarias para nuestro componente

jsx
function Dropdown({ items = [], dropdownTitle }) {
/*Resto del código*/
}

El Componente resivirá dos props:

  • items: los elementos que que conforman al menú, para este caso necesitamos un array de obejetos en el cual cada obejeto debe tener los valores slug y anchor, slug hacia donde apunte el enlace y anchor será el texto del enlace .
  • dropdownTitle: Esta propiedad será el texto visible en el botón que activará el desplegable

Hooks necesarios para el componente

jsx
/* ...Resto del código */
const activatorRef = useRef(null);
const dropdownListRef = useRef(null);
const [isOpen, setIsOpen] = useState(false);
/* ...Resto del código */
  • activatorRef y dropdownListRef: necesitaremos acceder a los nodos del dom tanto del botón que despliega el dropdown, como de la lista de enlaces.
  • También un típico estado toggle para controlar el comportamiento del desplegable, debe estar inicializado en falso ya que en un primer momento el dropdown estará cerrado.

Funciones para modificar el estado del dropdown

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: esta función lo unico que hará es cambiar el estado de false a true viceversa siempre que se haga click sobre el activatorRef.
  • keyHandler: Este dropdown debe funcionar sin usar el ratón para hacerlo usable y accesible para todos los usuarios, así que con esta función escuchamos el teclado y en caso de que la tecla pulsada sea Escape cambiamos el estado para cerrar el dropdown.
OjO

La función keyHandler la ejecutaremos en el evento onKeyUp del elemento que envuelve todo el componente.

  • clickOutsideHandler: Con esta función detectaremos cada vez que ocurra un click fuera del dropdown y en caso de haberlo echo cambiaremos el estado a falso. Por otro lado en caso que el click sea dentro de alguna de las dos referencias no se debe hacer nada por eso la primera condicional.
jsx
// Condicional para verificar que la referencia del dropdwon existe
if (dropdownListRef.current) {
//Condicional para ver si el click es dentro del dropdown, si es el caso no hacer nada y si no cambiar el estado a false
if (
dropdownListRef.current.contains(event.target) ||
activatorRef.current.contains(event.target)
) {
return;
}
setIsOpen(false);
}
}
Importante

La función clickOutsideHandler la ejecutaremos dentro de un useEffect ya que necesitamos detectar los eventos de mouse de todo el documento.

Hook useEffect para detectar los clicks fuera el desplegable

jsx
useEffect(() => {
if (isOpen) {
dropdownListRef.current.querySelector("a").focus();
document.addEventListener("mousedown", clickOutsideHandler);
} else {
document.addEventListener("mousedown", clickOutsideHandler);
}
}, [isOpen]);
  • En este gancho ejecutaremos la función clickOutsideHandler sobre todo el documento usando el evento mousedown.
  • En el caso que el desplegable este abierto enfocaremos el enlace dentro del dropdown para continuar con el flujo correcto de elementos clicables para los usuarios que recorren el documento con el tabulador del teclado.
  • Este efecto se debe ejecutar cada vez que cambie el estado isOpen así que se lo pasamos como dependencia al useEffect.

Jsx y renderizado del dropdown

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

Elemento contenedor le pasamos la clase dropdown_wrapperque definimos en nuestro css y en el evento onKeyUp la función keyHandler

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>

Al botón que actuará comoactivador le pasamos la clase dropdown_activator, ejecutamos la función clickHandler en el evento Onclick y le pasamos lareferencia activatorRef previamente definida con el gancho useRef.

Nota importante

el atributo aria-haspopup lo establecemos en true para indicar que es un elemento que aparecerá de forma interactiva y al atributo aria-controls le pasamos la prop dropdownTitle para identificarlo.

Aria es un conjunto de atributos que definen formas de hacer que el contenido web sea más accesible para usuarios con discapacidades aprende más en este enlace

Dentro del botón renderizamos el dropdownTitle y dependiendo del estado isOpen mostramos un svg con una flecha para arriba o para abajo

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>

Este elemento renderizará nuestra lista de enlaces así que le pasamos la referencia dropdownListRef previamente definida.

Agregamos la clase dropdown_item_list para darle algo de estilos y también usamos un operador ternario para agregar la clase active dependiendo si el estado isOpen es verdadero o falso. La clase active es la encargada de hacer que la lista se muestre o se oculte.

Por último recorremos el arreglo items y renderizamos un li con un enlace en el que el atributo href es item.slug y el texto del enlace item.anchor

js
// Estrutura del arreglo items
const items = [
{
slug: "/link1/",
anchor: "Link 1",
},
{
slug: "/link2/",
anchor: "Link 2",
},
{
slug: "/link3/",
anchor: "Link 3",
},
];
Sobre la funcionalidad de css

No voy a explicar paso a paso el código css de este ejemplo, creo que es muy fácil de entender y estoy seguro que tú puedes hacerlo mucho mejor que yo, espero haberte ayudado muchas gracias por visitar mi web.

Demostración final menu desplegable con reactjs

Juneiker Castillo freelance web developer

Soy Juneiker Castillo, un desarrollador web frontend apasionado por la programación y la creación de sitios web modernos rápidos y escalables, en fin un friki 🤓 de javascript enamorado de react js ⚛️.

Sobre mi