Módulo 16: Bootstrap: componentes
Objetivos del módulo
- Entender cómo funcionan los componentes de Bootstrap (clases + atributos
data-bs-*+ JavaScript) - Crear botones, alertas, badges, cards, listas y tablas
- Construir una navbar responsive con menú hamburguesa y desplegables
- Usar navegación por pestañas, breadcrumb y paginación
- Implementar componentes interactivos: dropdown, modal, offcanvas, carrusel, acordeón, collapse y toasts
- Inicializar con JavaScript los tooltips y popovers
- Indicar estados de carga con spinners, placeholders y barras de progreso
- Maquetar y validar formularios con Bootstrap
1. Cómo funcionan los componentes
📘 Concepto: Un componente de Bootstrap es un bloque de interfaz reutilizable formado por una estructura HTML concreta y unas clases. Los componentes interactivos (menús, modales, carruseles…) necesitan además el JavaScript de Bootstrap, que se activa mediante atributos
data-bs-*.
Piensa en los componentes como en piezas de LEGO con instrucciones: si respetas la estructura de la documentación (qué elemento va dentro de cuál y con qué clase), la pieza funciona. Si te saltas un paso, falla.
| Tipo | Ejemplos | ¿Necesita JS? |
|---|---|---|
| Solo CSS | Botones, badges, cards, list group, tablas, spinners, progress, breadcrumb | No |
Interactivos con data-bs-* | Navbar (toggler), dropdown, modal, offcanvas, carrusel, acordeón, collapse, tabs, alertas descartables | Sí, se activan solos |
| Interactivos que hay que inicializar | Tooltips, popovers, toasts | Sí, y con código JS propio |
Todos los ejemplos de este módulo suponen que tu página usa la plantilla del módulo anterior, con el CSS en el <head> y bootstrap.bundle.min.js al final del <body>.
| Atributo | Para qué sirve |
|---|---|
data-bs-toggle="..." | Qué componente activa este elemento (collapse, modal, dropdown, tab, offcanvas…) |
data-bs-target="#id" | Sobre qué elemento actúa |
data-bs-dismiss="..." | Cierra el componente que lo contiene (modal, alert, toast, offcanvas) |
⚠️ Importante: Los atributos
aria-*(aria-expanded,aria-controls,aria-label…) que verás en los ejemplos no son decorativos: hacen que el componente sea accesible para lectores de pantalla. Cópialos siempre.
2. Botones y grupos de botones
<!-- Variantes de color -->
<button type="button" class="btn btn-primary">Primario</button>
<button type="button" class="btn btn-secondary">Secundario</button>
<button type="button" class="btn btn-success">Éxito</button>
<button type="button" class="btn btn-danger">Peligro</button>
<button type="button" class="btn btn-link">Enlace</button>
<!-- Contorno, tamaños y estado -->
<button type="button" class="btn btn-outline-primary">Contorno</button>
<button type="button" class="btn btn-primary btn-lg">Grande</button>
<button type="button" class="btn btn-primary btn-sm">Pequeño</button>
<button type="button" class="btn btn-primary" disabled>Deshabilitado</button>
<!-- Un enlace con aspecto de botón -->
<a href="contacto.html" class="btn btn-success" role="button">Contactar</a>
<!-- Botones a todo el ancho en móvil y en línea desde md -->
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
<button class="btn btn-outline-secondary" type="button">Cancelar</button>
<button class="btn btn-primary" type="button">Guardar</button>
</div>
Grupos y botones conmutables
<!-- Grupo de botones -->
<div class="btn-group" role="group" aria-label="Alineación del texto">
<button type="button" class="btn btn-outline-primary">Izquierda</button>
<button type="button" class="btn btn-outline-primary">Centro</button>
<button type="button" class="btn btn-outline-primary">Derecha</button>
</div>
<!-- Selector tipo "segmented control" con radios reales (accesible) -->
<div class="btn-group" role="group" aria-label="Periodo de facturación">
<input type="radio" class="btn-check" name="periodo" id="mensual" autocomplete="off" checked>
<label class="btn btn-outline-primary" for="mensual">Mensual</label>
<input type="radio" class="btn-check" name="periodo" id="anual" autocomplete="off">
<label class="btn btn-outline-primary" for="anual">Anual</label>
</div>
💡 Consejo: Usa
<button>para acciones (enviar, abrir, cerrar) y<a>para navegar a otra página. La clase.btnsolo cambia el aspecto, no el significado.
3. Alertas y badges
3.1 Alertas
<div class="alert alert-success" role="alert">
<h4 class="alert-heading">¡Pedido confirmado!</h4>
<p class="mb-0">Recibirás un correo con los detalles. <a href="#" class="alert-link">Ver pedido</a>.</p>
</div>
<!-- Alerta que el usuario puede cerrar (necesita JS) -->
<div class="alert alert-warning alert-dismissible fade show" role="alert">
<strong>Atención:</strong> tu sesión caducará en 5 minutos.
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Cerrar"></button>
</div>
Variantes: alert-primary, -secondary, -success, -danger, -warning, -info, -light, -dark.
3.2 Badges
<h2>Novedades <span class="badge text-bg-secondary">Nuevo</span></h2>
<span class="badge rounded-pill text-bg-success">Disponible</span>
<!-- Contador de notificaciones sobre un botón -->
<button type="button" class="btn btn-primary position-relative">
Mensajes
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
12 <span class="visually-hidden">mensajes sin leer</span>
</span>
</button>
4. Cards
📘 Concepto: Una card (tarjeta) es un contenedor flexible con borde para agrupar contenido relacionado: imagen, título, texto, botones, cabecera y pie.
<div class="card" style="max-width: 20rem;">
<img src="img/paella.jpg" class="card-img-top" alt="Paella valenciana en una paellera">
<div class="card-body">
<h5 class="card-title">Paella valenciana</h5>
<p class="card-text">Receta tradicional con pollo, conejo, judía verde y garrofó.</p>
<a href="#" class="btn btn-primary">Ver receta</a>
</div>
<div class="card-footer text-body-secondary">Tiempo: 60 min</div>
</div>
| Clase | Elemento |
|---|---|
card | Contenedor |
card-header / card-footer | Cabecera y pie |
card-body | Cuerpo con relleno |
card-title, card-subtitle, card-text | Textos |
card-img-top, card-img-bottom | Imagen arriba o abajo |
card-img-overlay | Texto encima de la imagen |
stretched-link | Hace clicable toda la tarjeta |
4.1 Cards en rejilla con igual altura
💻 Ejemplo: Listado responsive de tarjetas.
h-100iguala las alturas ymt-autoempuja el botón al fondo.
<div class="container py-4">
<div class="row row-cols-1 row-cols-sm-2 row-cols-lg-3 g-4">
<div class="col">
<article class="card h-100 shadow-sm">
<img src="img/curso-html.jpg" class="card-img-top" alt="Portátil con código HTML">
<div class="card-body d-flex flex-column">
<span class="badge text-bg-primary align-self-start mb-2">Principiante</span>
<h3 class="card-title h5">HTML desde cero</h3>
<p class="card-text">Aprende la estructura de cualquier página web.</p>
<a href="#" class="btn btn-outline-primary mt-auto stretched-link">Más información</a>
</div>
</article>
</div>
<div class="col">
<article class="card h-100 shadow-sm">
<img src="img/curso-css.jpg" class="card-img-top" alt="Paleta de colores">
<div class="card-body d-flex flex-column">
<span class="badge text-bg-success align-self-start mb-2">Intermedio</span>
<h3 class="card-title h5">CSS moderno</h3>
<p class="card-text">Flexbox, Grid, variables, :has() y mucho más. Este texto es más largo a propósito para comprobar que las tarjetas mantienen la misma altura.</p>
<a href="#" class="btn btn-outline-primary mt-auto stretched-link">Más información</a>
</div>
</article>
</div>
<div class="col">
<article class="card h-100 shadow-sm">
<img src="img/curso-bootstrap.jpg" class="card-img-top" alt="Interfaz con componentes">
<div class="card-body d-flex flex-column">
<span class="badge text-bg-warning align-self-start mb-2">Avanzado</span>
<h3 class="card-title h5">Bootstrap 5</h3>
<p class="card-text">Maqueta interfaces profesionales en tiempo récord.</p>
<a href="#" class="btn btn-outline-primary mt-auto stretched-link">Más información</a>
</div>
</article>
</div>
</div>
</div>
4.2 Card horizontal
<div class="card mb-3" style="max-width: 540px;">
<div class="row g-0">
<div class="col-md-4">
<img src="img/autora.jpg" class="img-fluid rounded-start h-100 object-fit-cover" alt="Retrato de la autora">
</div>
<div class="col-md-8">
<div class="card-body">
<h5 class="card-title">Lucía Martín</h5>
<p class="card-text">Diseñadora UX con 10 años de experiencia.</p>
<p class="card-text"><small class="text-body-secondary">Madrid</small></p>
</div>
</div>
</div>
</div>
5. Navbar responsive
La navbar es la barra de navegación principal. Con navbar-expand-{bp} decides a partir de qué breakpoint se muestra desplegada; por debajo, se convierte en un menú hamburguesa.
💻 Ejemplo: Navbar completa con marca, enlaces, desplegable y buscador.
<nav class="navbar navbar-expand-lg bg-body-tertiary sticky-top">
<div class="container">
<!-- Marca / logo -->
<a class="navbar-brand fw-bold" href="index.html">☁️ Estudio Nube</a>
<!-- Botón hamburguesa: solo visible por debajo de lg -->
<button class="navbar-toggler" type="button"
data-bs-toggle="collapse" data-bs-target="#menuPrincipal"
aria-controls="menuPrincipal" aria-expanded="false" aria-label="Mostrar u ocultar el menú">
<span class="navbar-toggler-icon"></span>
</button>
<!-- Contenido que se pliega -->
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="index.html">Inicio</a>
</li>
<li class="nav-item">
<a class="nav-link" href="portfolio.html">Portfolio</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button"
data-bs-toggle="dropdown" aria-expanded="false">Servicios</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Diseño web</a></li>
<li><a class="dropdown-item" href="#">Tiendas online</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">Mantenimiento</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link disabled" aria-disabled="true">Próximamente</a>
</li>
</ul>
<form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Buscar" aria-label="Buscar">
<button class="btn btn-outline-success" type="submit">Buscar</button>
</form>
</div>
</div>
</nav>
| Clase / atributo | Función |
|---|---|
navbar-expand-lg | Desplegada desde lg; hamburguesa por debajo |
navbar-brand | Logo o nombre del sitio |
navbar-toggler | Botón hamburguesa |
collapse navbar-collapse + id | Zona plegable (el id debe coincidir con data-bs-target) |
navbar-nav / nav-item / nav-link | Lista de enlaces |
me-auto | Empuja lo siguiente (el buscador) a la derecha |
active + aria-current="page" | Página actual |
data-bs-theme="dark" + bg-dark | Navbar oscura (Bootstrap 5.3) |
⚠️ Importante: En Bootstrap 5.3, la clase
navbar-darkestá desaconsejada. Usa<nav class="navbar bg-dark" data-bs-theme="dark">para que el texto y el icono de la hamburguesa se vean claros.
6. Nav, tabs, breadcrumb y paginación
6.1 Nav y pestañas
<!-- Navegación simple en pestañas visuales (enlaces a otras páginas) -->
<ul class="nav nav-tabs">
<li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Perfil</a></li>
<li class="nav-item"><a class="nav-link" href="#">Ajustes</a></li>
</ul>
<!-- Variante "pastillas" a todo el ancho -->
<ul class="nav nav-pills nav-fill my-3">
<li class="nav-item"><a class="nav-link active" href="#">Todos</a></li>
<li class="nav-item"><a class="nav-link" href="#">Web</a></li>
<li class="nav-item"><a class="nav-link" href="#">Branding</a></li>
</ul>
Para que las pestañas cambien el contenido sin recargar la página, se usan data-bs-toggle="tab" y paneles tab-pane:
<ul class="nav nav-tabs" id="pestanasProducto" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="desc-tab" data-bs-toggle="tab" data-bs-target="#desc"
type="button" role="tab" aria-controls="desc" aria-selected="true">Descripción</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="espec-tab" data-bs-toggle="tab" data-bs-target="#espec"
type="button" role="tab" aria-controls="espec" aria-selected="false">Especificaciones</button>
</li>
</ul>
<div class="tab-content border border-top-0 p-3">
<div class="tab-pane fade show active" id="desc" role="tabpanel" aria-labelledby="desc-tab" tabindex="0">
Mochila impermeable de 20 litros.
</div>
<div class="tab-pane fade" id="espec" role="tabpanel" aria-labelledby="espec-tab" tabindex="0">
Peso: 450 g. Material: poliéster reciclado.
</div>
</div>
6.2 Breadcrumb (migas de pan)
<nav aria-label="Ruta de navegación">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="index.html">Inicio</a></li>
<li class="breadcrumb-item"><a href="tienda.html">Tienda</a></li>
<li class="breadcrumb-item active" aria-current="page">Mochilas</li>
</ol>
</nav>
<!-- Cambiar el separador con una variable CSS -->
<nav aria-label="Ruta" style="--bs-breadcrumb-divider: '›';">
<ol class="breadcrumb">...</ol>
</nav>
6.3 Paginación
<nav aria-label="Paginación de resultados">
<ul class="pagination justify-content-center">
<li class="page-item disabled"><a class="page-link">Anterior</a></li>
<li class="page-item active" aria-current="page"><a class="page-link" href="#">1</a></li>
<li class="page-item"><a class="page-link" href="?pagina=2">2</a></li>
<li class="page-item"><a class="page-link" href="?pagina=3">3</a></li>
<li class="page-item"><a class="page-link" href="?pagina=2">Siguiente</a></li>
</ul>
</nav>
Tamaños: pagination-lg y pagination-sm en el <ul>.
7. List group
<!-- Lista de enlaces con badge -->
<div class="list-group">
<a href="#" class="list-group-item list-group-item-action active" aria-current="true">Bandeja de entrada</a>
<a href="#" class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
Enviados <span class="badge text-bg-primary rounded-pill">14</span>
</a>
<a href="#" class="list-group-item list-group-item-action list-group-item-danger">Papelera</a>
</div>
<!-- Lista numerada sin bordes laterales (ideal dentro de cards) -->
<ol class="list-group list-group-numbered list-group-flush mt-3">
<li class="list-group-item">Precalienta el horno</li>
<li class="list-group-item">Mezcla los ingredientes</li>
<li class="list-group-item">Hornea 25 minutos</li>
</ol>
8. Dropdowns
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button"
data-bs-toggle="dropdown" aria-expanded="false">
Mi cuenta
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><h6 class="dropdown-header">Ana García</h6></li>
<li><a class="dropdown-item" href="#">Perfil</a></li>
<li><a class="dropdown-item active" href="#" aria-current="true">Pedidos</a></li>
<li><hr class="dropdown-divider"></li>
<li><button class="dropdown-item text-danger" type="button">Cerrar sesión</button></li>
</ul>
</div>
| Clase | Efecto |
|---|---|
dropdown / dropup / dropend / dropstart | Dirección en la que se abre |
dropdown-menu-end | Alinea el menú a la derecha del botón |
dropdown-header, dropdown-divider | Encabezado y separador |
dropdown-menu-dark | Menú oscuro (mejor: data-bs-theme="dark") |
💡 Consejo: Los dropdowns usan Popper para posicionarse sin salirse de la pantalla. Por eso necesitas
bootstrap.bundle.min.jsy nobootstrap.min.jsa secas.
9. Modal
📘 Concepto: Un modal es una ventana de diálogo que se superpone a la página y bloquea la interacción con el fondo hasta que se cierra. Úsalo para confirmaciones, formularios breves o detalles ampliados.
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#modalBorrar">
Eliminar cuenta
</button>
<div class="modal fade" id="modalBorrar" tabindex="-1" aria-labelledby="modalBorrarTitulo" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="modalBorrarTitulo">¿Eliminar tu cuenta?</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cerrar"></button>
</div>
<div class="modal-body">
Esta acción no se puede deshacer. Se borrarán todos tus datos.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="button" class="btn btn-danger">Sí, eliminar</button>
</div>
</div>
</div>
</div>
| Opción | Cómo |
|---|---|
| Tamaño | modal-sm, modal-lg, modal-xl en .modal-dialog |
| Pantalla completa | modal-fullscreen o modal-fullscreen-md-down |
| Centrado vertical | modal-dialog-centered |
| Contenido largo con scroll interno | modal-dialog-scrollable |
| No cerrar al pulsar fuera ni con Esc | data-bs-backdrop="static" data-bs-keyboard="false" en .modal |
También puedes controlarlo con JavaScript:
const modal = new bootstrap.Modal(document.getElementById('modalBorrar'));
modal.show(); // abrir
// modal.hide(); // cerrar
// Reaccionar a eventos: cuando el modal termina de abrirse
document.getElementById('modalBorrar').addEventListener('shown.bs.modal', () => {
console.log('Modal visible');
});
⚠️ Importante: Coloca el HTML de los modales al final del
<body>(fuera de contenedores conpositionooverflowespeciales) para evitar problemas de superposición. Y no abras un modal encima de otro.
10. Offcanvas
Un offcanvas es un panel lateral que se desliza desde un borde de la pantalla: ideal para menús en móvil, filtros o un carrito.
<button class="btn btn-primary" type="button" data-bs-toggle="offcanvas"
data-bs-target="#panelFiltros" aria-controls="panelFiltros">
Filtros
</button>
<div class="offcanvas offcanvas-end" tabindex="-1" id="panelFiltros" aria-labelledby="panelFiltrosTitulo">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="panelFiltrosTitulo">Filtrar productos</h5>
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Cerrar"></button>
</div>
<div class="offcanvas-body">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="filtroOferta">
<label class="form-check-label" for="filtroOferta">Solo ofertas</label>
</div>
</div>
</div>
Posiciones: offcanvas-start (izquierda), offcanvas-end (derecha), offcanvas-top, offcanvas-bottom. Con offcanvas-lg, el contenido se muestra normal desde lg y como panel por debajo.
11. Carousel
<div id="carruselPortada" class="carousel slide carousel-fade" data-bs-ride="carousel">
<div class="carousel-indicators">
<button type="button" data-bs-target="#carruselPortada" data-bs-slide-to="0" class="active" aria-current="true" aria-label="Diapositiva 1"></button>
<button type="button" data-bs-target="#carruselPortada" data-bs-slide-to="1" aria-label="Diapositiva 2"></button>
</div>
<div class="carousel-inner rounded-3">
<div class="carousel-item active" data-bs-interval="6000">
<img src="img/slide-1.jpg" class="d-block w-100" alt="Equipo trabajando en una oficina luminosa">
<div class="carousel-caption d-none d-md-block">
<h5>Diseño a medida</h5>
<p>Cada proyecto es único.</p>
</div>
</div>
<div class="carousel-item" data-bs-interval="6000">
<img src="img/slide-2.jpg" class="d-block w-100" alt="Web vista en un móvil y un portátil">
<div class="carousel-caption d-none d-md-block">
<h5>Siempre responsive</h5>
<p>Perfecto en cualquier pantalla.</p>
</div>
</div>
</div>
<button class="carousel-control-prev" type="button" data-bs-target="#carruselPortada" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Anterior</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#carruselPortada" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Siguiente</span>
</button>
</div>
| Atributo / clase | Efecto |
|---|---|
data-bs-ride="carousel" | Empieza a rotar automáticamente al cargar |
data-bs-interval="6000" | Milisegundos de cada diapositiva |
carousel-fade | Fundido en lugar de deslizamiento |
carousel-caption | Texto superpuesto |
⚠️ Importante: Los carruseles automáticos son poco accesibles y la mayoría de usuarios no pasa de la primera diapositiva. Si lo usas, pon el mensaje más importante en la primera, usa intervalos largos y no dependas de él para información crítica. Bootstrap pausa el carrusel al pasar el ratón y respeta
prefers-reduced-motionen las transiciones.
12. Accordion y collapse
12.1 Collapse
<button class="btn btn-outline-primary" type="button" data-bs-toggle="collapse"
data-bs-target="#masInfo" aria-expanded="false" aria-controls="masInfo">
Mostrar más información
</button>
<div class="collapse mt-2" id="masInfo">
<div class="card card-body">Contenido que aparece y desaparece con una animación.</div>
</div>
12.2 Accordion (preguntas frecuentes)
<div class="accordion" id="faq">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button" type="button" data-bs-toggle="collapse"
data-bs-target="#faq1" aria-expanded="true" aria-controls="faq1">
¿Cuánto tarda el envío?
</button>
</h2>
<div id="faq1" class="accordion-collapse collapse show" data-bs-parent="#faq">
<div class="accordion-body">Entre 24 y 48 horas en la península.</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
data-bs-target="#faq2" aria-expanded="false" aria-controls="faq2">
¿Puedo devolver un producto?
</button>
</h2>
<div id="faq2" class="accordion-collapse collapse" data-bs-parent="#faq">
<div class="accordion-body">Sí, dispones de 30 días naturales.</div>
</div>
</div>
</div>
| Detalle | Efecto |
|---|---|
show en el panel + botón sin collapsed | Panel abierto al cargar |
data-bs-parent="#faq" | Al abrir un panel se cierran los demás (quítalo para permitir varios abiertos) |
accordion-flush | Sin bordes ni esquinas, para insertarlo en otro contenedor |
13. Toasts
Los toasts son notificaciones breves que aparecen en una esquina (“Guardado correctamente”). No se muestran solos: hay que lanzarlos con JavaScript.
<button type="button" class="btn btn-success" id="btnGuardar">Guardar</button>
<div class="toast-container position-fixed bottom-0 end-0 p-3">
<div id="toastGuardado" class="toast" role="status" aria-live="polite" aria-atomic="true">
<div class="toast-header">
<strong class="me-auto">Estudio Nube</strong>
<small>ahora</small>
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Cerrar"></button>
</div>
<div class="toast-body">✅ Cambios guardados correctamente.</div>
</div>
</div>
<script>
const toast = bootstrap.Toast.getOrCreateInstance(document.getElementById('toastGuardado'));
document.getElementById('btnGuardar').addEventListener('click', () => toast.show());
</script>
💡 Consejo: Los toasts se ocultan solos a los 5 segundos. Cámbialo con
data-bs-delay="8000"o desactívalo condata-bs-autohide="false". Para mensajes importantes usarole="alert" aria-live="assertive".
14. Tooltips y popovers
⚠️ Importante: Por motivos de rendimiento, Bootstrap no activa automáticamente tooltips ni popovers. Si añades los atributos pero no el código de inicialización, no aparecerá nada.
<button type="button" class="btn btn-secondary"
data-bs-toggle="tooltip" data-bs-placement="top" data-bs-title="Se copiará al portapapeles">
Copiar enlace
</button>
<button type="button" class="btn btn-info"
data-bs-toggle="popover" data-bs-placement="right"
data-bs-title="Envío gratuito"
data-bs-content="En pedidos superiores a 50 € a la península.">
¿Envío gratis?
</button>
<!-- Popover que se cierra al hacer clic en cualquier otro sitio -->
<a tabindex="0" class="btn btn-link" role="button" data-bs-toggle="popover"
data-bs-trigger="focus" data-bs-content="Haz clic fuera para cerrarme">Info</a>
<script>
// Inicializar TODOS los tooltips de la página
document.querySelectorAll('[data-bs-toggle="tooltip"]')
.forEach(el => new bootstrap.Tooltip(el));
// Inicializar TODOS los popovers de la página
document.querySelectorAll('[data-bs-toggle="popover"]')
.forEach(el => new bootstrap.Popover(el));
</script>
| Opción | Valores |
|---|---|
data-bs-placement | top, bottom, start… (left/right también) |
data-bs-trigger | hover focus (tooltip por defecto), click (popover por defecto), focus |
data-bs-html="true" | Permite HTML en el contenido (con cuidado) |
💡 Consejo: No pongas información imprescindible solo en un tooltip: en móviles no existe el “hover”. Y nunca en elementos deshabilitados (
disabledno recibe eventos); envuélvelos en un<span tabindex="0">si lo necesitas.
15. Spinners, placeholders y progress
15.1 Spinners
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Cargando...</span>
</div>
<div class="spinner-grow text-success" role="status">
<span class="visually-hidden">Cargando...</span>
</div>
<!-- Botón en estado de carga -->
<button class="btn btn-primary" type="button" disabled>
<span class="spinner-border spinner-border-sm" aria-hidden="true"></span>
<span role="status">Enviando...</span>
</button>
15.2 Placeholders (esqueletos de carga)
<div class="card" aria-hidden="true" style="max-width: 18rem;">
<div class="card-body">
<h5 class="card-title placeholder-glow">
<span class="placeholder col-6"></span>
</h5>
<p class="card-text placeholder-glow">
<span class="placeholder col-7"></span>
<span class="placeholder col-4"></span>
<span class="placeholder col-6"></span>
</p>
<a class="btn btn-primary disabled placeholder col-6" aria-disabled="true"></a>
</div>
</div>
placeholder-glow hace un pulso de brillo; placeholder-wave, una onda.
15.3 Barras de progreso
<!-- Estructura de Bootstrap 5.3: los atributos ARIA van en .progress -->
<div class="progress" role="progressbar" aria-label="Progreso del curso"
aria-valuenow="65" aria-valuemin="0" aria-valuemax="100">
<div class="progress-bar" style="width: 65%">65%</div>
</div>
<!-- Rayada, animada y de color -->
<div class="progress mt-3" role="progressbar" aria-label="Subiendo archivo"
aria-valuenow="40" aria-valuemin="0" aria-valuemax="100">
<div class="progress-bar progress-bar-striped progress-bar-animated bg-success" style="width: 40%"></div>
</div>
16. Formularios con Bootstrap
16.1 Controles básicos
<div class="mb-3">
<label for="email" class="form-label">Correo electrónico</label>
<input type="email" class="form-control" id="email" aria-describedby="emailAyuda">
<div id="emailAyuda" class="form-text">Nunca compartiremos tu correo.</div>
</div>
<div class="mb-3">
<label for="provincia" class="form-label">Provincia</label>
<select class="form-select" id="provincia">
<option selected disabled value="">Elige...</option>
<option>Madrid</option>
<option>Valencia</option>
</select>
</div>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="boletin">
<label class="form-check-label" for="boletin">Suscribirme al boletín</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="notificaciones" checked>
<label class="form-check-label" for="notificaciones">Notificaciones</label>
</div>
Los radios se escriben igual que los checkbox (type="radio" con el mismo name); añade form-check-inline al contenedor para ponerlos en línea.
| Clase | Elemento |
|---|---|
form-label, form-text | Etiqueta y texto de ayuda |
form-control (-lg, -sm) | input, textarea |
form-select | select |
form-check, form-check-input, form-check-label | Checkbox y radio |
form-check-inline | Checks o radios en la misma línea |
form-switch | Checkbox con aspecto de interruptor |
form-range | Control deslizante (input type="range") |
16.2 Etiquetas flotantes e input groups
<!-- Etiqueta flotante: el input DEBE tener placeholder y la label va DESPUÉS -->
<div class="form-floating mb-3">
<input type="email" class="form-control" id="emailFlotante" placeholder="nombre@ejemplo.com">
<label for="emailFlotante">Correo electrónico</label>
</div>
<!-- Input group: texto o botones pegados al campo -->
<div class="input-group mb-3">
<span class="input-group-text">@</span>
<input type="text" class="form-control" placeholder="usuario" aria-label="Nombre de usuario">
</div>
<div class="input-group mb-3">
<input type="number" class="form-control" aria-label="Importe en euros">
<span class="input-group-text">€</span>
<button class="btn btn-outline-secondary" type="button">Aplicar</button>
</div>
16.3 Layout y validación
💻 Ejemplo: Formulario de contacto completo en rejilla, con validación de Bootstrap.
<form class="row g-3 needs-validation" novalidate>
<div class="col-md-6">
<label for="nombre" class="form-label">Nombre</label>
<input type="text" class="form-control" id="nombre" required>
<div class="valid-feedback">¡Perfecto!</div>
<div class="invalid-feedback">Escribe tu nombre.</div>
</div>
<div class="col-md-6">
<label for="correo" class="form-label">Correo</label>
<div class="input-group has-validation">
<span class="input-group-text">@</span>
<input type="email" class="form-control" id="correo" required>
<div class="invalid-feedback">Introduce un correo válido.</div>
</div>
</div>
<div class="col-md-4">
<label for="asunto" class="form-label">Asunto</label>
<select class="form-select" id="asunto" required>
<option selected disabled value="">Elige...</option>
<option>Presupuesto</option>
<option>Soporte</option>
</select>
<div class="invalid-feedback">Selecciona un asunto.</div>
</div>
<div class="col-md-8">
<label for="telefono" class="form-label">Teléfono (opcional)</label>
<input type="tel" class="form-control" id="telefono" pattern="[0-9]{9}">
<div class="invalid-feedback">Debe tener 9 dígitos.</div>
</div>
<div class="col-12">
<label for="mensaje" class="form-label">Mensaje</label>
<textarea class="form-control" id="mensaje" rows="4" minlength="10" required></textarea>
<div class="invalid-feedback">El mensaje debe tener al menos 10 caracteres.</div>
</div>
<div class="col-12">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="privacidad" required>
<label class="form-check-label" for="privacidad">Acepto la política de privacidad</label>
<div class="invalid-feedback">Debes aceptarla para continuar.</div>
</div>
</div>
<div class="col-12">
<button class="btn btn-primary" type="submit">Enviar</button>
</div>
</form>
<script>
// Validación de Bootstrap: usa la validación nativa de HTML5 y muestra los mensajes
document.querySelectorAll('.needs-validation').forEach(form => {
form.addEventListener('submit', event => {
if (!form.checkValidity()) {
event.preventDefault(); // no enviar si hay errores
event.stopPropagation();
}
form.classList.add('was-validated'); // activa los estilos verde/rojo
});
});
</script>
| Pieza | Función |
|---|---|
novalidate | Desactiva las burbujas nativas del navegador para mostrar las de Bootstrap |
required, pattern, minlength, type="email" | Reglas de validación HTML5 (las viste en el módulo 05) |
.was-validated en el <form> | Muestra los estilos :valid / :invalid |
valid-feedback / invalid-feedback | Mensajes que aparecen según el estado |
has-validation | Necesaria en input-group para redondear bien las esquinas |
is-valid / is-invalid | Marcar un campo manualmente (por ejemplo, tras validar en el servidor) |
⚠️ Importante: La validación en el navegador mejora la experiencia, pero no es seguridad. Cualquier usuario puede saltársela. El servidor siempre debe volver a validar los datos.
17. Tablas
<div class="table-responsive">
<table class="table table-striped table-hover align-middle">
<caption>Pedidos del último mes</caption>
<thead class="table-light">
<tr>
<th scope="col">#</th>
<th scope="col">Cliente</th>
<th scope="col">Estado</th>
<th scope="col" class="text-end">Importe</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">1001</th>
<td>Ana García</td>
<td><span class="badge text-bg-success">Enviado</span></td>
<td class="text-end">89,90 €</td>
</tr>
<tr class="table-warning">
<th scope="row">1002</th>
<td>Luis Pérez</td>
<td><span class="badge text-bg-warning">Pendiente</span></td>
<td class="text-end">45,00 €</td>
</tr>
</tbody>
</table>
</div>
| Clase | Efecto |
|---|---|
table | Estilo base (obligatoria) |
table-striped | Filas alternas sombreadas |
table-hover | Resalta la fila bajo el ratón |
table-bordered / table-borderless | Con o sin bordes en todas las celdas |
table-sm | Celdas más compactas |
table-dark, table-primary… | Color en tabla, fila o celda |
align-middle | Centrado vertical del contenido |
table-responsive (en un div contenedor) | Scroll horizontal en pantallas estrechas |
18. Errores comunes
| Síntoma | Causa | Solución |
|---|---|---|
| La hamburguesa no abre el menú | Falta el JS o el id no coincide con data-bs-target | Revisa el <script> y que #id sea idéntico |
| Dropdowns mal posicionados o sin abrir | Se cargó bootstrap.min.js sin Popper | Usa bootstrap.bundle.min.js |
| Los tooltips no aparecen | No se inicializaron | Añade el código new bootstrap.Tooltip(el) |
Atributos data-toggle no funcionan | Sintaxis de Bootstrap 4 | En Bootstrap 5 es data-bs-toggle |
| La etiqueta flotante se superpone al texto | Falta placeholder o la label está antes del input | placeholder obligatorio y label después |
Mensajes invalid-feedback nunca se ven | Falta .was-validated o el mensaje no es hermano del campo | Añade la clase con JS y coloca el mensaje justo después |
| El modal aparece detrás del fondo oscuro | El modal está dentro de un contenedor con position/z-index | Mueve el modal al final del <body> |
19. Ejercicios
Ejercicio 1: Panel de avisos
Crea una página con cuatro alertas (éxito, información, advertencia y error), cada una con un título alert-heading. Haz que la de advertencia se pueda cerrar. Añade un botón con un badge contador posicionado en la esquina.
Ejercicio 2: Catálogo con cards
Maqueta un catálogo de 6 productos con row-cols-*: imagen, badge de categoría, título, precio y botón “Añadir” alineado al fondo de cada tarjeta. Todas las tarjetas deben tener la misma altura aunque el texto varíe.
Ejercicio 3: Navbar profesional
Construye una navbar oscura (data-bs-theme="dark") que se expanda desde md, con logo, cuatro enlaces (uno activo con aria-current), un desplegable “Más” y un botón “Acceder” alineado a la derecha. Comprueba con el teclado (Tab, Enter, Esc) que todo es accesible.
Ejercicio 4: Ficha de producto
Crea una ficha con breadcrumb, un carrusel de 3 fotos con controles, pestañas (Descripción / Especificaciones / Opiniones) y un acordeón de preguntas frecuentes. Al pulsar “Comprar”, abre un modal de confirmación; al confirmar, ciérralo y muestra un toast “Añadido al carrito”.
Ejercicio 5: Ayudas contextuales
Añade a un formulario de registro tres tooltips (en iconos de ayuda junto a las etiquetas) y un popover que explique los requisitos de la contraseña. Recuerda inicializarlos con JavaScript.
Ejercicio 6: Formulario validado
Crea un formulario de reserva de mesa con: nombre, email, teléfono (9 dígitos), fecha, número de comensales (form-select), zona (radios inline: terraza/interior), alergias (textarea con form-floating) y aceptación de condiciones. Distribúyelo en rejilla de dos columnas desde md y valídalo con .was-validated.
Ejercicio 7: Panel de administración
Combina un offcanvas como menú lateral en móvil, una tabla responsive de usuarios con table-hover y badges de estado, paginación centrada, placeholders mientras “carga” (simula 2 segundos con setTimeout y luego sustituye el contenido) y una barra de progreso de “espacio utilizado”.
Resumen
| Componente | Clases / atributos clave | ¿JS? |
|---|---|---|
| Botones | btn btn-{color}, btn-outline-*, btn-lg, btn-group, btn-check | No |
| Alertas | alert alert-{color}, alert-dismissible, data-bs-dismiss="alert" | Solo para cerrar |
| Badges | badge text-bg-{color}, rounded-pill | No |
| Cards | card, card-body, card-title, card-img-top, h-100 | No |
| Navbar | navbar navbar-expand-lg, navbar-toggler, collapse navbar-collapse | Sí |
| Nav / tabs | nav nav-tabs, nav-pills, data-bs-toggle="tab", tab-pane | Para tabs |
| Breadcrumb | breadcrumb, breadcrumb-item, aria-current="page" | No |
| Paginación | pagination, page-item, page-link | No |
| List group | list-group, list-group-item, -action, -flush | No |
| Dropdown | dropdown, dropdown-toggle, dropdown-menu, dropdown-item | Sí |
| Modal | modal fade, modal-dialog, modal-content, data-bs-toggle="modal" | Sí |
| Offcanvas | offcanvas offcanvas-start, data-bs-toggle="offcanvas" | Sí |
| Carousel | carousel slide, carousel-item, data-bs-ride | Sí |
| Accordion / collapse | accordion-item, accordion-button, collapse, data-bs-parent | Sí |
| Toasts | toast, bootstrap.Toast.getOrCreateInstance(el).show() | Sí, inicializar |
| Tooltips / popovers | data-bs-toggle="tooltip", new bootstrap.Tooltip(el) | Sí, inicializar |
| Spinners / placeholders | spinner-border, placeholder, placeholder-glow | No |
| Progress | progress (con ARIA) + progress-bar | No |
| Formularios | form-control, form-select, form-check, form-floating, input-group | No |
| Validación | needs-validation, novalidate, .was-validated, invalid-feedback | Sí |
| Tablas | table, table-striped, table-hover, table-responsive | No |