Dropdown
.dropdown muestra un menú bajo un botón, con enlaces o acciones simples. Necesita ds-uikit.js.
- Dentro de
.dropdownvan el botón, condata-ds-toggle="dropdown"yaria-expanded="false", y el menúdropdown__menuconhidden. - El menú lleva enlaces (
<a href>) o botones (<button type="button">) con la clasedropdown__item. También aceptadropdown__headerydropdown__divider. dropdown__toggleen el botón agrega una flecha que gira al abrir (opcional).
<div class="dropdown"> <button type="button" class="btn dropdown__toggle" data-ds-toggle="dropdown" aria-expanded="false">Productos</button> <div class="dropdown__menu" hidden> <a class="dropdown__item" href="#">dsRest</a> <a class="dropdown__item" href="#">dsStore</a> <a class="dropdown__item" href="#">dsStepflow</a> </div></div>export default function DropdownBasico() { return ( <div className="dropdown"> <button type="button" className="btn dropdown__toggle" data-ds-toggle="dropdown" aria-expanded="false">Productos</button> <div className="dropdown__menu" hidden> <a className="dropdown__item" href="#">dsRest</a> <a className="dropdown__item" href="#">dsStore</a> <a className="dropdown__item" href="#">dsStepflow</a> </div> </div> );}Con botones
Sección titulada «Con botones»Para acciones en la misma página (duplicar, eliminar), usa <button type="button">. Al elegir un ítem, el menú se cierra y el foco vuelve al botón.
<div class="dropdown"> <button type="button" class="btn dropdown__toggle" data-ds-toggle="dropdown" aria-expanded="false">Acciones</button> <div class="dropdown__menu" hidden> <button type="button" class="dropdown__item">Duplicar</button> <button type="button" class="dropdown__item">Mover a otra carpeta</button> <button type="button" class="dropdown__item">Eliminar</button> </div></div>export default function DropdownBotones() { return ( <div className="dropdown"> <button type="button" className="btn dropdown__toggle" data-ds-toggle="dropdown" aria-expanded="false">Acciones</button> <div className="dropdown__menu" hidden> <button type="button" className="dropdown__item">Duplicar</button> <button type="button" className="dropdown__item">Mover a otra carpeta</button> <button type="button" className="dropdown__item">Eliminar</button> </div> </div> );}Alineado a la derecha
Sección titulada «Alineado a la derecha»Por defecto el menú se alinea con el borde izquierdo del botón. dropdown__menu--end lo alinea con el derecho: úsalo en botones pegados al borde derecho, como el menú de usuario del header.
<div class="dropdown"> <button type="button" class="btn dropdown__toggle" data-ds-toggle="dropdown" aria-expanded="false">Mi cuenta</button> <div class="dropdown__menu dropdown__menu--end" hidden> <a class="dropdown__item" href="#">Perfil</a> <a class="dropdown__item" href="#">Pedidos</a> <a class="dropdown__item" href="#">Cerrar sesión</a> </div></div>export default function DropdownDerecha() { return ( <div className="dropdown"> <button type="button" className="btn dropdown__toggle" data-ds-toggle="dropdown" aria-expanded="false">Mi cuenta</button> <div className="dropdown__menu dropdown__menu--end" hidden> <a className="dropdown__item" href="#">Perfil</a> <a className="dropdown__item" href="#">Pedidos</a> <a className="dropdown__item" href="#">Cerrar sesión</a> </div> </div> );}Si el menú no cabe a lo ancho, se corre para no salirse de la pantalla.
Hacia arriba
Sección titulada «Hacia arriba»dropdown--up abre el menú sobre el botón, por ejemplo en un pie de página.
<div class="dropdown dropdown--up"> <button type="button" class="btn dropdown__toggle" data-ds-toggle="dropdown" aria-expanded="false">Compartir</button> <div class="dropdown__menu" hidden> <a class="dropdown__item" href="#">Facebook</a> <a class="dropdown__item" href="#">LinkedIn</a> <button type="button" class="dropdown__item">Copiar enlace</button> </div></div>export default function DropdownArriba() { return ( <div className="dropdown dropdown--up"> <button type="button" className="btn dropdown__toggle" data-ds-toggle="dropdown" aria-expanded="false">Compartir</button> <div className="dropdown__menu" hidden> <a className="dropdown__item" href="#">Facebook</a> <a className="dropdown__item" href="#">LinkedIn</a> <button type="button" className="dropdown__item">Copiar enlace</button> </div> </div> );}Si no cabe hacia donde se abre y al otro lado hay más espacio, el menú se da vuelta solo (el JS agrega is-flipped a .dropdown).
Encabezado y separador
Sección titulada «Encabezado y separador»dropdown__header titula un grupo de ítems y <hr class="dropdown__divider"> los separa.
<div class="dropdown"> <button type="button" class="btn dropdown__toggle" data-ds-toggle="dropdown" aria-expanded="false">Mi cuenta</button> <div class="dropdown__menu" hidden> <div class="dropdown__header">Cuenta</div> <a class="dropdown__item" href="#">Perfil</a> <a class="dropdown__item" href="#">Configuración</a> <hr class="dropdown__divider"> <div class="dropdown__header">Ayuda</div> <a class="dropdown__item" href="#">Centro de ayuda</a> <a class="dropdown__item" href="#">Contacto</a> <hr class="dropdown__divider"> <button type="button" class="dropdown__item">Cerrar sesión</button> </div></div>export default function DropdownEncabezado() { return ( <div className="dropdown"> <button type="button" className="btn dropdown__toggle" data-ds-toggle="dropdown" aria-expanded="false">Mi cuenta</button> <div className="dropdown__menu" hidden> <div className="dropdown__header">Cuenta</div> <a className="dropdown__item" href="#">Perfil</a> <a className="dropdown__item" href="#">Configuración</a> <hr className="dropdown__divider" /> <div className="dropdown__header">Ayuda</div> <a className="dropdown__item" href="#">Centro de ayuda</a> <a className="dropdown__item" href="#">Contacto</a> <hr className="dropdown__divider" /> <button type="button" className="dropdown__item">Cerrar sesión</button> </div> </div> );}Activo y deshabilitado
Sección titulada «Activo y deshabilitado»- Actual:
aria-current="page"en el enlace a la página en que estás (ois-active, solo visual). - Deshabilitado: un enlace sin
hrefy conaria-disabled="true", o un botón condisabled. No se puede elegir y las flechas lo saltan.
<div class="dropdown"> <button type="button" class="btn dropdown__toggle" data-ds-toggle="dropdown" aria-expanded="false">Categorías</button> <div class="dropdown__menu" hidden> <a class="dropdown__item" href="#" aria-current="page">Todas</a> <a class="dropdown__item" href="#">Diseño</a> <a class="dropdown__item" href="#">Desarrollo</a> <a class="dropdown__item" aria-disabled="true">Eventos</a> </div></div>
<div class="dropdown"> <button type="button" class="btn dropdown__toggle" data-ds-toggle="dropdown" aria-expanded="false">Archivo</button> <div class="dropdown__menu" hidden> <button type="button" class="dropdown__item">Guardar</button> <button type="button" class="dropdown__item" disabled>Deshacer</button> <button type="button" class="dropdown__item">Exportar</button> </div></div>export default function DropdownEstados() { return ( <> <div className="dropdown"> <button type="button" className="btn dropdown__toggle" data-ds-toggle="dropdown" aria-expanded="false">Categorías</button> <div className="dropdown__menu" hidden> <a className="dropdown__item" href="#" aria-current="page">Todas</a> <a className="dropdown__item" href="#">Diseño</a> <a className="dropdown__item" href="#">Desarrollo</a> <a className="dropdown__item" aria-disabled="true">Eventos</a> </div> </div>
<div className="dropdown"> <button type="button" className="btn dropdown__toggle" data-ds-toggle="dropdown" aria-expanded="false">Archivo</button> <div className="dropdown__menu" hidden> <button type="button" className="dropdown__item">Guardar</button> <button type="button" className="dropdown__item" disabled>Deshacer</button> <button type="button" className="dropdown__item">Exportar</button> </div> </div> </> );}Dentro de un modal
Sección titulada «Dentro de un modal»El menú no se corta con el scroll del cuerpo del modal: dentro de un contenedor con overflow, el JS lo ubica con position: fixed. Escape cierra primero el dropdown y después el modal.
<button type="button" class="btn btn--primary" data-ds-toggle="modal" data-ds-target="#modal-compartir">Compartir archivo</button>
<div class="modal" id="modal-compartir" role="dialog" aria-modal="true" aria-labelledby="modal-compartir-titulo" hidden> <div class="modal__backdrop"></div> <div class="modal__dialog"> <div class="modal__header"> <h2 class="modal__title" id="modal-compartir-titulo">Compartir archivo</h2> <button type="button" class="modal__close" data-ds-dismiss="modal" aria-label="Cerrar"></button> </div> <div class="modal__body"> <p>Elige quién puede ver el archivo.</p> <div class="dropdown"> <button type="button" class="btn dropdown__toggle" data-ds-toggle="dropdown" aria-expanded="false">Permisos</button> <div class="dropdown__menu" hidden> <button type="button" class="dropdown__item">Solo yo</button> <button type="button" class="dropdown__item">Mi equipo</button> <button type="button" class="dropdown__item">Cualquiera con el enlace</button> </div> </div> </div> <div class="modal__footer"> <button type="button" class="btn" data-ds-dismiss="modal">Cancelar</button> <button type="button" class="btn btn--primary" data-ds-dismiss="modal">Listo</button> </div> </div></div>export default function DropdownModal() { return ( <> <button type="button" className="btn btn--primary" data-ds-toggle="modal" data-ds-target="#modal-compartir">Compartir archivo</button>
<div className="modal" id="modal-compartir" role="dialog" aria-modal="true" aria-labelledby="modal-compartir-titulo" hidden> <div className="modal__backdrop" /> <div className="modal__dialog"> <div className="modal__header"> <h2 className="modal__title" id="modal-compartir-titulo">Compartir archivo</h2> <button type="button" className="modal__close" data-ds-dismiss="modal" aria-label="Cerrar" /> </div> <div className="modal__body"> <p>Elige quién puede ver el archivo.</p> <div className="dropdown"> <button type="button" className="btn dropdown__toggle" data-ds-toggle="dropdown" aria-expanded="false">Permisos</button> <div className="dropdown__menu" hidden> <button type="button" className="dropdown__item">Solo yo</button> <button type="button" className="dropdown__item">Mi equipo</button> <button type="button" className="dropdown__item">Cualquiera con el enlace</button> </div> </div> </div> <div className="modal__footer"> <button type="button" className="btn" data-ds-dismiss="modal">Cancelar</button> <button type="button" className="btn btn--primary" data-ds-dismiss="modal">Listo</button> </div> </div> </div> </> );}JavaScript
Sección titulada «JavaScript»- Se abre con un clic en el botón (
data-ds-toggle="dropdown"abre el.dropdownque lo contiene) o con la flecha abajo, que además enfoca el primer ítem (la flecha arriba enfoca el último). - Se cierra con un clic fuera, con Escape, al elegir un ítem y al salir con Tab. Con Escape o al elegir un ítem, el foco vuelve al botón.
- Solo hay un dropdown abierto a la vez: al abrir uno, se cierra el anterior.
- Actualiza
aria-expandeden el botón. Aparece y desaparece con un fundido y un leve desplazamiento. Queda oculto conhidden, sin quitarlo del DOM.
| Atributo | Qué hace |
|---|---|
data-ds-toggle="dropdown" |
En el botón: abre o cierra el .dropdown que lo contiene. Con data-ds-target, el indicado. |
data-ds-dismiss="dropdown" |
Cierra el dropdown que contiene al elemento (por ejemplo, un botón dentro de un formulario en el menú). |
const cuenta = dsUIKit.dropdown('#menu-cuenta'); // .dropdown o selectorcuenta.open();cuenta.close();cuenta.toggle();cuenta.isOpen; // true o false
// Saber qué ítem se eligiódocument.querySelector('#menu-cuenta').addEventListener('ds:close', (e) => { if (e.detail.trigger?.matches('.dropdown__item')) console.log(e.detail.trigger.textContent);});En React, los atributos funcionan igual. Para abrirlo desde código: dropdown(ref.current).open() (con import { dropdown } from './js/ds-uikit.esm.js').
| Evento | Cuándo |
|---|---|
ds:open |
Antes de abrirse. Con preventDefault() se cancela. |
ds:opened |
Al terminar de abrirse. |
ds:close |
Antes de cerrarse. Cancelable. |
ds:closed |
Al terminar de cerrarse. |
event.detail.trigger es el botón que lo abrió o cerró, el ítem elegido, o null si fue con un clic fuera, Escape, Tab o la API. Más detalles en JavaScript.
Accesibilidad
Sección titulada «Accesibilidad»- Sigue el patrón disclosure (botón que muestra u oculta contenido), sin
role="menu": sirve para enlaces y acciones simples. Los lectores de pantalla anuncian el botón como contraído o expandido gracias aaria-expanded. - Teclado: Enter o Espacio abren y cierran; la flecha abajo abre y enfoca el primer ítem; dentro del menú, las flechas, Inicio y Fin recorren los ítems (saltan los deshabilitados); Tab avanza al siguiente elemento y cierra al salir; Escape cierra y devuelve el foco al botón.
- Si el botón solo tiene un ícono, ponle
aria-label(por ejemplo,aria-label="Menú de usuario"). - El ítem de la página actual lleva
aria-current="page".is-activesolo cambia el aspecto. dropdown__headeres texto, no un título: no altera la estructura de encabezados de la página.- Con «reducir movimiento» activado, abre y cierra sin transición.
| Clase | Uso |
|---|---|
dropdown |
Contenedor del botón y el menú. |
dropdown--up |
El menú se abre hacia arriba. |
dropdown__toggle |
En el botón (opcional): flecha que gira al abrir. |
dropdown__menu |
Menú. Lleva hidden mientras está cerrado. |
dropdown__menu--end |
Alinea el menú con el borde derecho del botón. |
dropdown__item |
Ítem: <a href> o <button type="button">. |
dropdown__header |
Título de un grupo de ítems. |
dropdown__divider |
Separador (<hr>). |
is-active |
Ítem actual (solo visual; mejor aria-current). |
is-disabled |
Ítem deshabilitado (solo visual; mejor aria-disabled="true" o disabled). |
Variables
Sección titulada «Variables»| Variable | Valor por defecto | Uso |
|---|---|---|
--ds-dropdown-min-width | 10rem | Ancho mínimo del menú (si el botón es más ancho, toma su ancho). |
--ds-dropdown-gap | var(--ds-space-1) | Separación entre el botón y el menú. |
--ds-dropdown-padding-y | var(--ds-space-2) | Espacio vertical interno del menú. |
--ds-dropdown-bg | var(--ds-color-surface) | Fondo del menú. |
--ds-dropdown-color | var(--ds-color-text) | Color del texto. |
--ds-dropdown-border-color | var(--ds-color-border) | Color del borde del menú. |
--ds-dropdown-radius | var(--ds-radius-md) | |
--ds-dropdown-shadow | var(--ds-shadow-md) | |
--ds-dropdown-font-size | var(--ds-font-size-sm) | |
--ds-dropdown-item-padding-y | var(--ds-space-2) | Espacio vertical interno del ítem. |
--ds-dropdown-item-padding-x | var(--ds-space-4) | Espacio horizontal interno de ítems y encabezados. |
--ds-dropdown-item-gap | var(--ds-space-2) | Separación entre ícono y texto. |
--ds-dropdown-item-hover-color | var(--ds-dropdown-color) | Color del ítem en hover y con foco. |
--ds-dropdown-item-hover-bg | color-mix(in srgb, var(--ds-color-text) 6%, var(--ds-color-surface)) | Fondo del ítem en hover y con foco. |
--ds-dropdown-item-active-color | var(--ds-color-primary-hover) | Color del ítem actual. |
--ds-dropdown-item-active-bg | color-mix(in srgb, var(--ds-color-primary) 12%, var(--ds-color-surface)) | Fondo del ítem actual. |
--ds-dropdown-item-active-font-weight | var(--ds-font-weight-semibold) | Peso del ítem actual. |
--ds-dropdown-item-disabled-opacity | 0.5 | Opacidad de un ítem deshabilitado. |
--ds-dropdown-icon-size | 1.25em | Tamaño de los íconos de los ítems. |
--ds-dropdown-header-color | var(--ds-color-text-muted) | Color del encabezado (dropdown__header). |
--ds-dropdown-header-font-size | var(--ds-font-size-xs) | Tamaño del encabezado. |
--ds-dropdown-divider-color | var(--ds-color-border) | Color del separador (dropdown__divider). |
--ds-dropdown-focus-color | var(--ds-color-primary) | Color del contorno de foco. |
--ds-dropdown-offset | 0.5rem | Desplazamiento del menú hacia el botón al abrir y cerrar. |
--ds-dropdown-transition-duration | var(--ds-duration-fast) | Duración del fundido al abrir y cerrar. |
--ds-dropdown-z-index | var(--ds-z-dropdown) | Capa del menú (z-index). |