Cómo convertir las pestañas de Bootstrap en un menú desplegable
Spanish (Español) translation by Carlos (you can also view the original English article)
Anteriormente, analizamos varias extensiones de Bootstrap 4. Hoy te mostraré cómo convertir las pills de Bootstrap en un menú desplegable. Lo más importante es que mantendremos ambos componentes sincronizados. Utilizaremos botones para el diseño de los equipos de escritorio y un menú desplegable para las pantallas de los dispositivos móviles.
Nota: Para este ejercicio, utilizaré la última versión estable de Bootstrap 4 (v4.6). Tan pronto como v5 sea estable, podría revisar nuevamente este tema e implementar también una solución para esta versión.
Nuestra extensión de Bootstrap
¡Revisa la demostración final! Haz clic en un elemento desplegable para que veas cómo aparece el panel de pestañas correspondiente. Asimismo, abre la demostración y mira tu diseño en una pantalla grande. Verás que los botones sustituyen al menú desplegable sin cambiar el panel activo.
Así es como aparecen en sus dos estados:



¿Por qué hacer esto? De manera predeterminada, las tabs y pills de Bootstrap no tienen un comportamiento responsivo, aparte de apilarse/envolverse. Un menú desplegable es una solución mucho más elegante para pantallas pequeñas.
Tabs y pills de Bootstrap
Las tabs de Bootstrap nos ayudan a dividir el contenido en varias secciones que se alojen bajo una base común. En un momento dado, solamente una de estas secciones es visible. Imagínatelas como si fueran las pestañas del navegador; la diferencia es que no tienes que cambiar de página para verlas todas.



Las pills de Bootstrap son básicamente tabs con un diseño diferente.



Para este ejemplo, usaremos pills. No obstante, puedes usar tabs con la misma facilidad. Todo lo que tiene que hacer es cambiar las clases y el marcado correspondiente.
1. Incluye los recursos de Bootstrap
Para iniciar, incluiremos los archivos de CSS y JavaScript que se requieren en nuestra demostración de Codepen:






2. Crea el diseño del proyecto
Nuestro proyecto en pantallas medianas y superiores (≥768px) se verá de esta forma:



Aquí tendremos un contenedor que incluirá:
- Las pills.
- El contenido de cada pill (paneles de pestañas).
- Los enlaces de redes sociales.
El marcado que se requiere:
1 |
<div class="tabs-to-dropdown"> |
2 |
<div class="nav-wrapper d-flex align-items-center justify-content-between"> |
3 |
<ul class="nav nav-pills d-none d-md-flex" id="pills-tab" role="tablist"> |
4 |
<li class="nav-item" role="presentation"> |
5 |
<a class="nav-link active" id="pills-company-tab" data-toggle="pill" href="#pills-company" role="tab" aria-controls="pills-company" aria-selected="true">...</a> |
6 |
</li>
|
7 |
<li class="nav-item" role="presentation"> |
8 |
<a class="nav-link" id="pills-product-tab" data-toggle="pill" href="#pills-product" role="tab" aria-controls="pills-product" aria-selected="false">... </a> |
9 |
</li>
|
10 |
<li class="nav-item" role="presentation"> |
11 |
<a class="nav-link" id="pills-news-tab" data-toggle="pill" href="#pills-news" role="tab" aria-controls="pills-news" aria-selected="false">... </a> |
12 |
</li>
|
13 |
<li class="nav-item" role="presentation"> |
14 |
<a class="nav-link" id="pills-contact-tab" data-toggle="pill" href="#pills-contact" role="tab" aria-controls="pills-contact" aria-selected="false">...</a> |
15 |
</li>
|
16 |
</ul>
|
17 |
|
18 |
<ul class="list-group list-group-horizontal"> |
19 |
<!-- social links here -->
|
20 |
</ul>
|
21 |
</div>
|
22 |
|
23 |
<div class="tab-content" id="pills-tabContent"> |
24 |
<div class="tab-pane fade show active" id="pills-company" role="tabpanel" aria-labelledby="pills-company-tab">...</div> |
25 |
<div class="tab-pane fade" id="pills-product" role="tabpanel" aria-labelledby="pills-product-tab">...</div> |
26 |
<div class="tab-pane fade" id="pills-news" role="tabpanel" aria-labelledby="pills-news-tab">...</div> |
27 |
<div class="tab-pane fade" id="pills-contact" role="tabpanel" aria-labelledby="pills-contact-tab">...</div> |
28 |
</div>
|
29 |
</div>
|
La mayor parte de este marcado proviene de la documentación de Bootstrap, solamente han cambiado algunas cosas.
En pantallas más pequeñas (<768 px), reemplazaremos las pills con un menú desplegable como este:



Pero, aquí está la parte interesante: si revisas el marcado anterior, notarás que no hemos definido ningún componente desplegable. Lo incluiremos en la página de manera dinámica mediante JavaScript.
3. Añade algunos estilos básicos
Luego, especificaremos algunos estilos básicos para nuestro
proyecto. Cosas bastante sencillas. Simplemente anularemos algunos de los estilos predeterminados de Bootstrap para que se ajusten a nuestro diseño. Por ejemplo, cambiaremos la estética de las pills y le daremos a .container-fluid un ancho máximo de 1250 px.
Los estilos:
1 |
:root { |
2 |
--darkgreen: #005361; |
3 |
--white: #fff; |
4 |
}
|
5 |
|
6 |
* { |
7 |
padding: 0; |
8 |
margin: 0; |
9 |
box-sizing: border-box; |
10 |
}
|
11 |
|
12 |
body, |
13 |
.tabs-to-dropdown .dropdown-toggle, |
14 |
.tabs-to-dropdown .dropdown-item { |
15 |
font-size: 1.3rem; |
16 |
}
|
17 |
|
18 |
.tabs-to-dropdown .nav-wrapper { |
19 |
padding: 15px; |
20 |
box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.12); |
21 |
}
|
22 |
|
23 |
.tabs-to-dropdown .nav-wrapper a { |
24 |
color: var(--darkgreen); |
25 |
}
|
26 |
|
27 |
.tabs-to-dropdown .nav-pills .nav-link.active { |
28 |
background-color: var(--darkgreen); |
29 |
}
|
30 |
|
31 |
.tabs-to-dropdown .nav-pills li:not(:last-child) { |
32 |
margin-right: 30px; |
33 |
}
|
34 |
|
35 |
.tabs-to-dropdown .tab-content .container-fluid { |
36 |
max-width: 1250px; |
37 |
padding-top: 70px; |
38 |
padding-bottom: 70px; |
39 |
}
|
40 |
|
41 |
.tabs-to-dropdown .dropdown-menu { |
42 |
border: none; |
43 |
box-shadow: 0px 5px 14px rgba(0, 0, 0, 0.08); |
44 |
}
|
45 |
|
46 |
.tabs-to-dropdown .dropdown-item { |
47 |
padding: 14px 28px; |
48 |
}
|
49 |
|
50 |
.tabs-to-dropdown .dropdown-item:active { |
51 |
color: var(--white); |
52 |
}
|
53 |
|
54 |
@media (min-width: 1280px) { |
55 |
.tabs-to-dropdown .nav-wrapper { |
56 |
padding: 15px 30px; |
57 |
}
|
58 |
}
|
4. Añade el código JavaScript
Empezaremos por crear un bucle por todos los elementos .tabs-to-dropdown y, para cada uno de ellos, se llevarán a cabo varias acciones:
1 |
const $tabsToDropdown = $(".tabs-to-dropdown"); |
2 |
|
3 |
$tabsToDropdown.each(function () { |
4 |
const $this = $(this); |
5 |
const $pills = $this.find(’a[data-toggle="pill"]’); |
6 |
|
7 |
generateDropdownMarkup($this); |
8 |
|
9 |
const $dropdown = $this.find(".dropdown"); |
10 |
const $dropdownLinks = $this.find(".dropdown-menu a"); |
11 |
|
12 |
$dropdown.on("show.bs.dropdown", showDropdownHandler); |
13 |
$dropdownLinks.on("click", clickHandler); |
14 |
$pills.on("shown.bs.tab", shownTabsDropdown); |
15 |
});
|
Nota 1: Utilizaremos un bucle aquí, pues asumimos que nuestra página puede contener más de un elemento .tabs-to-dropdown.



Nota 2: En el código anterior, primero crearemos el menú
desplegable y luego lo referenciaremos. Por este motivo, la función generateDropdownMarkup() viene antes de las variables relacionadas con el menú desplegable.
Crea el componente desplegable
Primero, llamaremos a la función generateDropdownMarkup() y le pasaremos el elemento .tabs-to-dropdown
correspondiente.
Aquí está la declaración de la función:
1 |
function generateDropdownMarkup(container) { |
2 |
const $navWrapper = container.find(".nav-wrapper"); |
3 |
const $navPills = container.find(".nav-pills"); |
4 |
const firstTextLink = $navPills.find("li:first-child a").text(); |
5 |
const $items = $navPills.find("li"); |
6 |
const markup = ` |
7 |
<div class="dropdown d-md-none">
|
8 |
<button class="btn dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
9 |
${firstTextLink} |
10 |
</button>
|
11 |
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
|
12 |
${generateDropdownLinksMarkup($items)} |
13 |
</div>
|
14 |
</div>
|
15 |
`; |
16 |
$navWrapper.prepend(markup); |
17 |
}
|
Esta función generará el código para el componente de conmutación desplegable y lo antepondrá al elemento .nav-wrapper. Para lograr esto, dentro de esta función, llamaremos a
generateDropdownLinksMarkup() que se encargará de crear los elementos del menú desplegable. Su texto coincidirá con el texto de las pills correspondientes.
Aquí está la declaración de la función:
1 |
function generateDropdownLinksMarkup(items) { |
2 |
let markup = ""; |
3 |
items.each(function () { |
4 |
const textLink = $(this).find("a").text(); |
5 |
markup += `<a class="dropdown-item" href="#">${textLink}</a>`; |
6 |
});
|
7 |
|
8 |
return markup; |
9 |
}
|
Después de ejecutar estas funciones, el marcado desplegable se verá de esta forma:



Oculta el elemento duplicado del menú
Cada vez que abrimos el menú desplegable, debemos evitar que el botón de conmutación también aparezca como un elemento del menú. Así que, en lugar de esto:

Queremos esto:

En este caso, cuando el panel de la pestaña «Company» esté activo, el elemento del menú «Company» debe estar oculto.
Para implementar esta funcionalidad, aprovecharemos el evento show.bs.dropdown que ofrece Bootstrap:
1 |
...
|
2 |
|
3 |
const $dropdown = $this.find(".dropdown"); |
4 |
|
5 |
$dropdown.on("show.bs.dropdown", showDropdownHandler); |
6 |
|
7 |
function showDropdownHandler(e) { |
8 |
// works also
|
9 |
//const $this = $(this);
|
10 |
const $this = $(e.target); |
11 |
const $dropdownToggle = $this.find(".dropdown-toggle"); |
12 |
const dropdownToggleText = $dropdownToggle.text().trim(); |
13 |
const $dropdownMenuLinks = $this.find(".dropdown-menu a"); |
14 |
const dNoneClass = "d-none"; |
15 |
$dropdownMenuLinks.each(function () { |
16 |
const $this = $(this); |
17 |
if ($this.text() == dropdownToggleText) { |
18 |
$this.addClass(dNoneClass); |
19 |
} else { |
20 |
$this.removeClass(dNoneClass); |
21 |
}
|
22 |
});
|
23 |
}
|
Dentro de la función de callback, haremos lo siguiente:
- Tomaremos el texto del botón de conmutación.
- Crearemos un bucle a través de los enlaces del menú y analizaremos si su texto coincide con el texto del botón.
- Si este es el caso, recibirán la clase
d-none. De otra manera, perderán este.
Sincroniza el menú desplegable con las pills
El último paso y el más difícil es sincronizar el menú desplegable y las pills.



Así que, primero, cada vez que hagamos clic en un elemento del menú desplegable, debe aparecer el panel de pestañas correspondiente.
Aquí está el código que se requiere:
1 |
...
|
2 |
|
3 |
const $dropdownLinks = $this.find(".dropdown-menu a"); |
4 |
|
5 |
$dropdownLinks.on("click", clickHandler); |
6 |
|
7 |
function clickHandler(e) { |
8 |
e.preventDefault(); |
9 |
const $this = $(this); |
10 |
const index = $this.index(); |
11 |
const text = $this.text(); |
12 |
$this.closest(".dropdown").find(".dropdown-toggle").text(`${text}`); |
13 |
$this
|
14 |
.closest($tabsToDropdown) |
15 |
.find(`.nav-pills li:eq(${index}) a`) |
16 |
.tab("show"); |
17 |
}
|
Dentro de la función de callback, haremos lo siguiente:
- Tomaremos index y el texto del enlace objetivo/activo.
- Reemplazaremos el texto del botón de conmutación con el texto del enlace activo.
- Seleccionaremos la pill cuyo index coincida con el index del enlace activo y muestre su panel asociado.
Luego, cada vez que hagamos clic en una pill, el texto del botón de conmutación debería cambiar y coincidir con el texto de la pill correspondiente.
Para aplicar esta funcionalidad, aprovecharemos el evento shown.bs.tab que proporciona Bootstrap:
1 |
...
|
2 |
|
3 |
const $pills = $this.find(’a[data-toggle="pill"]’); |
4 |
|
5 |
$pills.on("shown.bs.tab", shownTabsHandler); |
6 |
|
7 |
function shownTabsHandler(e) { |
8 |
// works also
|
9 |
//const $this = $(this);
|
10 |
const $this = $(e.target); |
11 |
const index = $this.parent().index(); |
12 |
const $parent = $this.closest($tabsToDropdown); |
13 |
const $targetDropdownLink = $parent.find(".dropdown-menu a").eq(index); |
14 |
const targetDropdownLinkText = $targetDropdownLink.text(); |
15 |
$parent.find(".dropdown-toggle").text(targetDropdownLinkText); |
16 |
}
|
Dentro de la función de callback, haremos lo siguiente:
- Tomaremos el index de la pill activa.
- Tomaremos el texto del elemento del menú desplegable cuyo index coincida con el index de la pill activa.
- Reemplazaremos el texto del botón de conmutación desplegable con el texto del elemento del menú correspondiente.
Conclusión
¡Eso es todo, amigos! Gracias por seguir otro tutorial de Bootstrap 4. Esperemos que esta extensión te haya dado una idea de cómo manejar las pills en un diseño móvil. Como viste, convertirlos en un componente desplegable completamente funcional no es tan difícil como parece.
¡Ahora haz el mismo trabajo con las tabs!
Si te ha resultado útil esta solución o si tienes alguna pregunta, ¡déjame un comentario! Asimismo, hazme saber si deseas ver otras extensiones de Bootstrap.
Este es un recordatorio de nuestra extensión:
Como siempre, ¡muchas gracias por leer!
Tutoriales y recursos de Bootstrap


BootstrapMás de 25 impresionantes plantillas de Bootstrap para probar en 2021Paula Borowska

Bootstrap 4Consejo rápido: cómo personalizar el componente de acordeón de Bootstrap 4George Martsoukos

Bootstrap 4Cómo añadir enlaces profundos al componente de pestañas de Bootstrap 4George Martsoukos

Bootstrap15 plantillas de administración de Bootstrap llenas de funcionesIan Yates

Bootstrap 4Cómo hacer que el menú desplegable de la barra de navegación de Bootstrap funcione al situar el puntero (on hover)George Martsoukos



