Dropdown
Menú desplegable para agrupar acciones o mostrar opciones de navegación contextual.
1. Default (interactivo)
Mostrar HTML
<div class="gn-dropdown-menu" data-dropdown-demo>
<button type="button" class="gn-dropdown-menu__trigger" aria-haspopup="menu" aria-expanded="false" id="dd-default-trigger">
<span class="gn-dropdown-menu__trigger-text">Seleccionar acción</span>
<svg focusable="false" class="gn-dropdown-menu__trigger-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<path d="M18 9.40039L12 15.4004L6 9.40039L7.0752 8.3252L12 13.25L16.9248 8.3252L18 9.40039Z" fill="currentColor"/>
</svg>
</button>
<div class="gn-dropdown-menu__panel" aria-labelledby="dd-default-trigger" role="menu">
<ul class="gn-dropdown-menu__list" role="none">
<li class="gn-dropdown-menu__item" role="none"><button type="button" class="gn-dropdown-menu__action" role="menuitem">Editar documento</button></li>
<li class="gn-dropdown-menu__item" role="none"><button type="button" class="gn-dropdown-menu__action" role="menuitem">Mover a carpeta</button></li>
<li class="gn-dropdown-menu__item" role="none"><button type="button" class="gn-dropdown-menu__action" role="menuitem">Archivar</button></li>
</ul>
</div>
</div>
Mostrar JS
document.addEventListener('DOMContentLoaded', () => {
window.GNDS.initDropdownMenus();
});
2. Disabled
Mostrar HTML
<div class="gn-dropdown-menu" data-dropdown-demo>
<button type="button" class="gn-dropdown-menu__trigger" aria-haspopup="menu" aria-expanded="false" disabled id="dd-disabled-trigger">
<span class="gn-dropdown-menu__trigger-text">No disponible</span>
<svg focusable="false" class="gn-dropdown-menu__trigger-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<path d="M18 9.40039L12 15.4004L6 9.40039L7.0752 8.3252L12 13.25L16.9248 8.3252L18 9.40039Z" fill="currentColor"/>
</svg>
</button>
<div class="gn-dropdown-menu__panel" aria-labelledby="dd-disabled-trigger" role="menu">
<ul class="gn-dropdown-menu__list" role="none">
<li class="gn-dropdown-menu__item" role="none"><button type="button" class="gn-dropdown-menu__action" role="menuitem">Acción</button></li>
</ul>
</div>
</div>
Mostrar JS
document.addEventListener('DOMContentLoaded', () => {
window.GNDS.initDropdownMenus();
});
3. Icon only (interactivo)
Mostrar HTML
<div class="gn-dropdown-menu" data-dropdown-demo>
<button type="button" class="gn-dropdown-menu__trigger gn-dropdown-menu__trigger--icon-only" aria-haspopup="menu" aria-expanded="false" id="dd-icon-only-trigger" aria-label="Más opciones">
<svg focusable="false" class="gn-dropdown-menu__trigger-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="5" r="2" fill="currentColor" />
<circle cx="12" cy="12" r="2" fill="currentColor" />
<circle cx="12" cy="19" r="2" fill="currentColor" />
</svg>
</button>
<div class="gn-dropdown-menu__panel" aria-labelledby="dd-icon-only-trigger" role="menu">
<ul class="gn-dropdown-menu__list" role="none">
<li class="gn-dropdown-menu__item" role="none"><button type="button" class="gn-dropdown-menu__action" role="menuitem">Editar</button></li>
<li class="gn-dropdown-menu__item" role="none"><button type="button" class="gn-dropdown-menu__action" role="menuitem">Eliminar</button></li>
</ul>
</div>
</div>
Mostrar JS
document.addEventListener('DOMContentLoaded', () => {
window.GNDS.initDropdownMenus();
});
4. Ghost (interactivo)
Mostrar HTML
<div class="gn-dropdown-menu" data-dropdown-demo>
<button type="button" class="gn-dropdown-menu__trigger gn-dropdown-menu__trigger--ghost" aria-haspopup="menu" aria-expanded="false" id="dd-ghost-trigger">
<span class="gn-dropdown-menu__trigger-text">Acciones</span>
<svg focusable="false" class="gn-dropdown-menu__trigger-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<path d="M18 9.40039L12 15.4004L6 9.40039L7.0752 8.3252L12 13.25L16.9248 8.3252L18 9.40039Z" fill="currentColor"/>
</svg>
</button>
<div class="gn-dropdown-menu__panel" aria-labelledby="dd-ghost-trigger" role="menu">
<ul class="gn-dropdown-menu__list" role="none">
<li class="gn-dropdown-menu__item" role="none"><button type="button" class="gn-dropdown-menu__action" role="menuitem">Duplicar</button></li>
<li class="gn-dropdown-menu__item" role="none"><button type="button" class="gn-dropdown-menu__action" role="menuitem">Compartir</button></li>
</ul>
</div>
</div>
Mostrar JS
document.addEventListener('DOMContentLoaded', () => {
window.GNDS.initDropdownMenus();
});
5. Filters (multiselección, interactivo)
Mostrar HTML
<div class="gn-dropdown-menu gn-dropdown-menu--filters" data-dropdown-demo data-dropdown-filters>
<button type="button" class="gn-dropdown-menu__trigger gn-dropdown-menu__trigger--ghost" aria-haspopup="true" aria-expanded="false" id="dd-filters-trigger">
<span class="gn-dropdown-menu__trigger-text">Filtros demo</span>
<svg focusable="false" class="gn-dropdown-menu__trigger-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<path d="M18 9.40039L12 15.4004L6 9.40039L7.0752 8.3252L12 13.25L16.9248 8.3252L18 9.40039Z" fill="currentColor"/>
</svg>
</button>
<div class="gn-dropdown-menu__panel" role="group" aria-label="Opciones de filtro">
<ul class="gn-dropdown-menu__list">
<li class="gn-dropdown-menu__item"><button type="button" class="gn-dropdown-menu__filter-option" aria-pressed="false">Mañana</button></li>
<li class="gn-dropdown-menu__item"><button type="button" class="gn-dropdown-menu__filter-option gn-dropdown-menu__filter-option--checked" aria-pressed="true">Tarde</button></li>
<li class="gn-dropdown-menu__item"><button type="button" class="gn-dropdown-menu__filter-option" aria-pressed="false">Online</button></li>
<li class="gn-dropdown-menu__item"><button type="button" class="gn-dropdown-menu__filter-option gn-dropdown-menu__filter-option--checked" aria-pressed="true">Presencial</button></li>
</ul>
</div>
</div>
Mostrar JS
document.addEventListener('DOMContentLoaded', () => {
window.GNDS.initDropdownMenus();
});
6. Default SM (interactivo)
Mostrar HTML
<div class="gn-dropdown-menu gn-dropdown-menu--sm" data-dropdown-demo>
<button type="button" class="gn-dropdown-menu__trigger" aria-haspopup="menu" aria-expanded="false" id="dd-default-sm-trigger">
<span class="gn-dropdown-menu__trigger-text">Seleccionar acción</span>
<svg focusable="false" class="gn-dropdown-menu__trigger-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<path d="M18 9.40039L12 15.4004L6 9.40039L7.0752 8.3252L12 13.25L16.9248 8.3252L18 9.40039Z" fill="currentColor"/>
</svg>
</button>
<div class="gn-dropdown-menu__panel" aria-labelledby="dd-default-sm-trigger" role="menu">
<ul class="gn-dropdown-menu__list" role="none">
<li class="gn-dropdown-menu__item" role="none"><button type="button" class="gn-dropdown-menu__action" role="menuitem">Editar documento</button></li>
<li class="gn-dropdown-menu__item" role="none"><button type="button" class="gn-dropdown-menu__action" role="menuitem">Mover a carpeta</button></li>
<li class="gn-dropdown-menu__item" role="none"><button type="button" class="gn-dropdown-menu__action" role="menuitem">Archivar</button></li>
</ul>
</div>
</div>
Mostrar JS
document.addEventListener('DOMContentLoaded', () => {
window.GNDS.initDropdownMenus();
});
7. Filters SM (multiselección, interactivo)
Mostrar HTML
<div class="gn-dropdown-menu gn-dropdown-menu--sm gn-dropdown-menu--filters" data-dropdown-demo data-dropdown-filters>
<button type="button" class="gn-dropdown-menu__trigger gn-dropdown-menu__trigger--ghost" aria-haspopup="true" aria-expanded="false" id="dd-filters-sm-trigger">
<span class="gn-dropdown-menu__trigger-text">Filtros demo</span>
<svg focusable="false" class="gn-dropdown-menu__trigger-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<path d="M18 9.40039L12 15.4004L6 9.40039L7.0752 8.3252L12 13.25L16.9248 8.3252L18 9.40039Z" fill="currentColor"/>
</svg>
</button>
<div class="gn-dropdown-menu__panel" role="group" aria-label="Opciones de filtro">
<ul class="gn-dropdown-menu__list">
<li class="gn-dropdown-menu__item"><button type="button" class="gn-dropdown-menu__filter-option" aria-pressed="false">Mañana</button></li>
<li class="gn-dropdown-menu__item"><button type="button" class="gn-dropdown-menu__filter-option gn-dropdown-menu__filter-option--checked" aria-pressed="true">Tarde</button></li>
<li class="gn-dropdown-menu__item"><button type="button" class="gn-dropdown-menu__filter-option" aria-pressed="false">Online</button></li>
<li class="gn-dropdown-menu__item"><button type="button" class="gn-dropdown-menu__filter-option gn-dropdown-menu__filter-option--checked" aria-pressed="true">Presencial</button></li>
</ul>
</div>
</div>
Mostrar JS
document.addEventListener('DOMContentLoaded', () => {
window.GNDS.initDropdownMenus();
});
8. Selector de valor — MD (listbox, interactivo)
- Español
- Euskera
- English
Mostrar HTML
<div class="gn-dropdown-menu gn-dropdown-menu--selector">
<button
type="button"
class="gn-dropdown-menu__trigger"
aria-haspopup="listbox"
aria-expanded="false"
aria-controls="dd-lang-listbox"
id="dd-lang-trigger">
<span class="gn-dropdown-menu__trigger-text">Español</span>
<svg focusable="false" class="gn-dropdown-menu__trigger-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<path d="M18 9.40039L12 15.4004L6 9.40039L7.0752 8.3252L12 13.25L16.9248 8.3252L18 9.40039Z" fill="currentColor"/>
</svg>
</button>
<ul
id="dd-lang-listbox"
class="gn-dropdown-menu__panel"
role="listbox"
aria-labelledby="dd-lang-trigger">
<li class="gn-dropdown-menu__option" role="option" aria-selected="true" tabindex="0">Español</li>
<li class="gn-dropdown-menu__option" role="option" aria-selected="false" tabindex="-1">Euskera</li>
<li class="gn-dropdown-menu__option" role="option" aria-selected="false" tabindex="-1">English</li>
</ul>
</div>
Mostrar JS
document.addEventListener('DOMContentLoaded', () => {
window.GNDS.initDropdownMenus();
});
9. Selector de valor — SM (listbox, interactivo)
- Español
- Euskera
- English
Mostrar HTML
<div class="gn-dropdown-menu gn-dropdown-menu--sm gn-dropdown-menu--selector">
<button
type="button"
class="gn-dropdown-menu__trigger"
aria-haspopup="listbox"
aria-expanded="false"
aria-controls="dd-lang-sm-listbox"
id="dd-lang-sm-trigger">
<span class="gn-dropdown-menu__trigger-text">Español</span>
<svg focusable="false" class="gn-dropdown-menu__trigger-icon" viewBox="0 0 24 24" fill="none" aria-hidden="true" xmlns="http://www.w3.org/2000/svg">
<path d="M18 9.40039L12 15.4004L6 9.40039L7.0752 8.3252L12 13.25L16.9248 8.3252L18 9.40039Z" fill="currentColor"/>
</svg>
</button>
<ul
id="dd-lang-sm-listbox"
class="gn-dropdown-menu__panel"
role="listbox"
aria-labelledby="dd-lang-sm-trigger">
<li class="gn-dropdown-menu__option" role="option" aria-selected="true" tabindex="0">Español</li>
<li class="gn-dropdown-menu__option" role="option" aria-selected="false" tabindex="-1">Euskera</li>
<li class="gn-dropdown-menu__option" role="option" aria-selected="false" tabindex="-1">English</li>
</ul>
</div>
Mostrar JS
document.addEventListener('DOMContentLoaded', () => {
window.GNDS.initDropdownMenus();
});
El componente dropdown-menu cubre varios patrones de interacción con semánticas ARIA distintas. Elegir el patrón correcto es crítico: declarar un rol ARIA sin implementar el comportamiento de teclado que implica comunica información errónea a tecnologías asistivas.
Cuándo usar cada variante
Selector de valor (idioma, estado, provincia): patrón listbox. El trigger declara aria-haspopup="listbox", el panel tiene role="listbox" y cada opción role="option" con aria-selected.
Menú de acciones (Editar, Eliminar, Descargar): patrón menu. El trigger usa aria-haspopup="menu", el panel tiene role="menu" y cada ítem role="menuitem". No usar aria-selected en este patrón.
Filtros multiselección (varios activos simultáneamente): botones <button> con aria-pressed="true|false" agrupados en role="group". Esta variante está implementada como gn-dropdown-menu--filters.
Filtros de selección única (solo uno activo a la vez): preferir <input type="radio"> nativo. Si se necesita estética de desplegable, usar role="listbox" con role="option" y aria-selected, implementando el mismo comportamiento de teclado que la variante selector.
Filtros tipo pestañas (cambian paneles de contenido): usar el patrón tablist/tab/tabpanel del componente gn-tabs.
A. Selector de valor (variante --selector)
El trigger declara aria-haspopup="listbox" y aria-controls apuntando al id del panel. El panel es un <ul role="listbox"> con aria-labelledby apuntando al trigger. Cada opción es <li role="option"> con aria-selected="true|false" y tabindex="-1" (la opción seleccionada usa tabindex="0").
Comportamiento de teclado requerido:
ArrowDown/ArrowUp: mueve el foco entre opciones.Enter/Space: selecciona la opción enfocada, cierra el panel y devuelve el foco al trigger.Escape: cierra el panel sin seleccionar y devuelve el foco al trigger.Home/End: primera o última opción.- Al abrir, el foco va directamente a la opción actualmente seleccionada (o a la primera si no hay ninguna).
- El texto visible del trigger se actualiza con el valor seleccionado.
B. Menú de acciones
El trigger usa aria-haspopup="menu". El panel tiene role="menu" y cada ítem role="menuitem". Seleccionar una acción ejecuta la acción y cierra el panel. No se usa aria-selected. El ítem que ejecuta una acción destructiva puede tener aria-label más descriptivo si el texto visible no es suficiente.
C. Filtros multiselección — toggle buttons
Cada opción es un <button type="button"> con aria-pressed="true" (activo) o aria-pressed="false" (inactivo). Los botones se agrupan en role="group" con aria-label que describe el conjunto. El panel permanece abierto tras cada interacción. El trigger muestra el recuento de filtros activos entre paréntesis.
aria-pressed="true" significa filtro activado. aria-pressed="false" significa filtro desactivado. No usar aria-selected en este patrón.
D. Filtros de selección única
Para selección única dentro de un formulario, usar <input type="radio"> con <fieldset> y <legend>: el navegador gestiona exclusividad, foco y anuncios automáticamente.
Si se necesita estética de chip en un panel desplegable, puede usarse role="listbox" con role="option" y aria-selected. En ese caso, al seleccionar una opción se deben desmarcar todas las demás (aria-selected="false"). Nunca usar aria-selected en botones sueltos fuera de un contexto listbox, tablist o grid.
E. Filtros tipo pestañas
Solo aplicar el patrón tablist si el filtro realmente cambia entre paneles de contenido diferenciados. Cada tab tiene role="tab", aria-selected, id y aria-controls apuntando al id de su panel. Cada panel tiene role="tabpanel" y aria-labelledby apuntando al tab. Ver componente gn-tabs para implementación completa con navegación de teclado por flechas.
Errores comunes
- No usar
role="group"como sustituto derole="listbox". - No usar
role="listbox"en menús de acciones. - No añadir
aria-selecteda botones fuera delistbox,tablistogrid. - No usar
aria-pressedpara opciones de selección única que representan un valor. - No declarar
aria-labelledbyyaria-labela la vez en el mismo elemento:aria-labelgana, pero la duplicidad genera confusión. - No declarar un rol ARIA complejo sin implementar el comportamiento de teclado que implica.