Módulo 17: Bootstrap: personalización
Objetivos del módulo
- Sobrescribir estilos de Bootstrap con CSS propio controlando la especificidad
- Personalizar Bootstrap en tiempo real con sus variables CSS (
--bs-*) - Implementar modo oscuro con
data-bs-themey un conmutador en JavaScript - Usar Bootstrap Icons como fuente y como SVG
- Compilar un Bootstrap a medida con Sass: colores, variables, mapas e importación parcial
- Crear utilidades propias con la API de utilidades
- Aplicar buenas prácticas de accesibilidad con Bootstrap
- Saber cuándo no usar Bootstrap y cómo se compara con Tailwind CSS
1. Tres niveles de personalización
Personalizar Bootstrap es como reformar un piso de alquiler amueblado: puedes cambiar cojines y cortinas (CSS propio), pintar las paredes (variables CSS) o hacer obra y tirar tabiques (Sass). Cada nivel requiere más esfuerzo, pero da más control.
| Nivel | Herramienta | Requiere instalación | Control | Ideal para |
|---|---|---|---|---|
| 1 | CSS propio que sobrescribe | No (funciona con CDN) | Bajo-medio | Ajustes puntuales |
| 2 | Variables CSS --bs-* | No (funciona con CDN) | Medio | Colores, fuentes, temas, modo oscuro |
| 3 | Sass | Sí (Node.js + npm) | Total | Marca propia, reducir tamaño, utilidades nuevas |
flowchart LR
A["¿Qué quieres cambiar?"] --> B{"¿Un detalle concreto?"}
B -- Sí --> C["CSS propio"]
B -- No --> D{"¿Colores, fuentes, modo oscuro en tiempo real?"}
D -- Sí --> E["Variables CSS --bs-*"]
D -- No --> F["Sass: compilar Bootstrap a medida"]
2. Sobrescribir estilos con CSS propio
2.1 Orden de carga
La regla básica de la cascada (módulo 07): con la misma especificidad, gana la última regla. Por eso tu CSS va después de Bootstrap:
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="css/estilos.css" rel="stylesheet"> <!-- después -->
/* estilos.css: misma especificidad que en Bootstrap (una clase) → gana la nuestra */
.card {
border-radius: 1rem;
border: none;
box-shadow: 0 0.5rem 1.5rem rgb(0 0 0 / 0.08);
}
2.2 Cuando no basta: especificidad
Algunos selectores de Bootstrap son más específicos. Usa DevTools (clic derecho → Inspeccionar → pestaña Styles) para ver qué regla se aplica y con qué selector.
/* Bootstrap usa ".navbar-nav .nav-link.active" (3 clases).
Una sola clase .nav-link no basta; igualamos la especificidad: */
.navbar-nav .nav-link.active {
font-weight: 700;
border-bottom: 2px solid currentColor;
}
💡 Consejo: Crea clases propias en lugar de modificar las de Bootstrap para todo el sitio:
.card-productoen vez de cambiar.card. Así no rompes otras tarjetas y el código explica su intención.
<div class="card card-producto">...</div>
.card-producto { transition: transform 0.2s; }
.card-producto:hover { transform: translateY(-4px); }
2.3 Capas de cascada con Bootstrap
Como viste en el módulo 14, puedes meter Bootstrap en una capa para que cualquier regla tuya gane sin preocuparte por la especificidad:
/* estilos.css (enlaza SOLO este fichero en el HTML) */
@layer bootstrap, tema;
@import url("https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css") layer(bootstrap);
@layer tema {
.nav-link { font-weight: 600; } /* gana a .navbar-nav .nav-link.active */
}
⚠️ Importante: Bootstrap usa
!importanten sus utilidades (.d-none,.mt-3…) para que siempre ganen. Recuerda que con!importantel orden de capas se invierte: dentro de una capa anterior, el!importantde Bootstrap seguirá ganando a tus!important. Es el comportamiento deseado: las utilidades deben ganar.
⚠️ Importante: Evita llenar tu CSS de
!importantpara “ganar” a Bootstrap. Es una señal de que debes usar una clase propia, igualar la especificidad, usar capas o, mejor aún, variables CSS o Sass.
3. Variables CSS de Bootstrap
3.1 Variables globales
Bootstrap 5.3 expone cientos de custom properties con el prefijo --bs- en :root. Puedes verlas en DevTools seleccionando el elemento <html>.
| Variable | Qué controla |
|---|---|
--bs-body-font-family | Fuente de todo el documento |
--bs-body-font-size | Tamaño base del texto |
--bs-body-color / --bs-body-bg | Color de texto y de fondo de la página |
--bs-primary, --bs-secondary… | Colores del tema (en hexadecimal) |
--bs-primary-rgb | El mismo color en canales RGB (13, 110, 253) para usar con opacidad |
--bs-link-color / --bs-link-hover-color | Enlaces |
--bs-border-radius | Radio de esquinas por defecto |
--bs-border-color | Color de bordes |
--bs-secondary-color | Texto atenuado (text-body-secondary) |
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap");
:root {
--bs-body-font-family: "Inter", system-ui, sans-serif;
--bs-body-color: #1f2933;
--bs-border-radius: 0.75rem;
/* Para cambiar un color de tema, cambia la versión hex Y la rgb */
--bs-primary: #6f42c1;
--bs-primary-rgb: 111, 66, 193;
--bs-link-color: #6f42c1;
--bs-link-color-rgb: 111, 66, 193;
}
💡 Consejo: Para cargar una fuente de Google Fonts es preferible enlazarla con
<link>en el<head>(conpreconnect) en lugar de@import, porque@importretrasa la descarga. Aquí usamos@importsolo para que el ejemplo quepa en un fichero.
3.2 Variables de componente
⚠️ Importante: Cambiar
--bs-primaryno cambia el color de.btn-primary. Los botones y muchos componentes tienen sus propias variables locales (calculadas al compilar con Sass), como--bs-btn-bg. Para modificarlos, cambia esas variables en el componente.
/* Botón primario morado, con sus estados */
.btn-primary {
--bs-btn-bg: #6f42c1;
--bs-btn-border-color: #6f42c1;
--bs-btn-hover-bg: #59339d;
--bs-btn-hover-border-color: #59339d;
--bs-btn-active-bg: #4c2a86;
--bs-btn-active-border-color: #4c2a86;
--bs-btn-disabled-bg: #6f42c1;
--bs-btn-disabled-border-color: #6f42c1;
--bs-btn-focus-shadow-rgb: 111, 66, 193;
}
/* Navbar: color de los enlaces */
.navbar {
--bs-navbar-color: rgb(255 255 255 / 0.8);
--bs-navbar-hover-color: #fff;
--bs-navbar-active-color: #fff;
}
/* Cards: relleno y radio */
.card {
--bs-card-spacer-y: 1.5rem;
--bs-card-spacer-x: 1.5rem;
--bs-card-border-radius: 1rem;
}
💡 Consejo: Cada página de componente de la documentación tiene una sección “CSS → Variables” con la lista completa de sus variables. Es la forma más limpia de personalizar un componente sin Sass.
3.3 Variables a nivel local
Como son variables CSS, puedes cambiarlas solo en una zona o elemento, incluso desde el atributo style:
<!-- Solo esta sección tiene enlaces naranjas -->
<section style="--bs-link-color-rgb: 253, 126, 20;">
<a href="#">Enlace naranja</a>
</section>
<!-- Un botón concreto con otro radio -->
<button class="btn btn-dark" style="--bs-btn-border-radius: 2rem;">Píldora</button>
4. Modo oscuro con data-bs-theme
4.1 Activar el modo oscuro
Desde la versión 5.3, Bootstrap incluye modos de color. Basta un atributo:
<!-- Toda la página en modo oscuro -->
<html lang="es" data-bs-theme="dark">
<!-- O solo un fragmento: una tarjeta oscura dentro de una página clara -->
<div class="card" data-bs-theme="dark">
<div class="card-body">Tarjeta oscura</div>
</div>
📘 Concepto:
data-bs-theme="dark"redefine variables como--bs-body-bg,--bs-body-coloro--bs-border-color. Los componentes y utilidades que usan esas variables (bg-body,bg-body-tertiary,text-body-secondary, cards, modales, formularios…) se adaptan solos.
⚠️ Importante: Clases como
bg-light,bg-whiteotext-darkson colores fijos y no cambian con el modo. Si quieres que se adapten, usa las versiones semánticas:bg-body,bg-body-tertiary,text-body,text-body-emphasis,border,*-subtle,*-emphasis.
4.2 Conmutador claro / oscuro / automático
💻 Ejemplo: Conmutador completo que recuerda la preferencia del usuario y respeta la del sistema operativo.
<!doctype html>
<html lang="es" data-bs-theme="light">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Modo oscuro</title>
<!-- Aplicamos el tema ANTES de pintar la página para evitar un parpadeo -->
<script>
(() => {
const guardado = localStorage.getItem('tema') || 'auto';
const oscuroSistema = window.matchMedia('(prefers-color-scheme: dark)').matches;
const tema = guardado === 'auto' ? (oscuroSistema ? 'dark' : 'light') : guardado;
document.documentElement.setAttribute('data-bs-theme', tema);
})();
</script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar bg-body-tertiary border-bottom">
<div class="container">
<span class="navbar-brand">Mi web</span>
<div class="dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button"
data-bs-toggle="dropdown" aria-expanded="false" aria-label="Cambiar tema">
<i class="bi bi-circle-half" aria-hidden="true"></i> Tema
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><button class="dropdown-item" type="button" data-tema="light">
<i class="bi bi-sun-fill me-2" aria-hidden="true"></i>Claro</button></li>
<li><button class="dropdown-item" type="button" data-tema="dark">
<i class="bi bi-moon-stars-fill me-2" aria-hidden="true"></i>Oscuro</button></li>
<li><button class="dropdown-item" type="button" data-tema="auto">
<i class="bi bi-circle-half me-2" aria-hidden="true"></i>Automático</button></li>
</ul>
</div>
</div>
</nav>
<main class="container py-5">
<h1>Hola</h1>
<p class="text-body-secondary">Cambia el tema desde el menú superior.</p>
<div class="card"><div class="card-body">Esta tarjeta se adapta sola.</div></div>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"></script>
<script>
const consultaOscuro = window.matchMedia('(prefers-color-scheme: dark)');
function aplicarTema(preferencia) {
const tema = preferencia === 'auto'
? (consultaOscuro.matches ? 'dark' : 'light')
: preferencia;
document.documentElement.setAttribute('data-bs-theme', tema);
// Marcar la opción activa en el menú (accesible)
document.querySelectorAll('[data-tema]').forEach(boton => {
const activo = boton.dataset.tema === preferencia;
boton.classList.toggle('active', activo);
boton.setAttribute('aria-pressed', activo);
});
}
// Al pulsar una opción: guardar y aplicar
document.querySelectorAll('[data-tema]').forEach(boton => {
boton.addEventListener('click', () => {
localStorage.setItem('tema', boton.dataset.tema);
aplicarTema(boton.dataset.tema);
});
});
// Si el usuario está en "auto" y cambia el tema del sistema, lo seguimos
consultaOscuro.addEventListener('change', () => {
if ((localStorage.getItem('tema') || 'auto') === 'auto') aplicarTema('auto');
});
aplicarTema(localStorage.getItem('tema') || 'auto');
</script>
</body>
</html>
| Pieza | Para qué |
|---|---|
Script en el <head> | Fija el tema antes de pintar: evita el “destello” blanco al cargar en modo oscuro |
localStorage | Recuerda la elección entre visitas |
matchMedia('(prefers-color-scheme: dark)') | Lee la preferencia del sistema operativo |
Evento change | Reacciona si el sistema cambia de modo (por ejemplo, al anochecer) |
4.3 Personalizar los colores del modo oscuro
/* Ajustar el modo oscuro de Bootstrap a tu marca */
[data-bs-theme="dark"] {
--bs-body-bg: #0f172a;
--bs-body-color: #e2e8f0;
--bs-tertiary-bg: #1e293b;
--bs-border-color: #334155;
}
/* Tus propios estilos también pueden depender del modo */
[data-bs-theme="dark"] .hero {
background-image: linear-gradient(135deg, #1e1b4b, #0f172a);
}
5. Bootstrap Icons
Bootstrap Icons es una librería oficial y gratuita con más de 2.000 iconos SVG. Se puede usar con cualquier proyecto (no necesita Bootstrap).
5.1 Como fuente de iconos (CDN)
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css">
<!-- Icono decorativo junto a un texto: lo ocultamos a los lectores de pantalla -->
<button class="btn btn-primary">
<i class="bi bi-download me-1" aria-hidden="true"></i> Descargar
</button>
<!-- Icono que transmite información por sí solo: necesita texto alternativo -->
<a href="https://github.com/" class="fs-3 link-body-emphasis" aria-label="GitHub">
<i class="bi bi-github" aria-hidden="true"></i>
</a>
<!-- Tamaño y color con utilidades: los iconos son texto -->
<i class="bi bi-heart-fill text-danger fs-1" aria-hidden="true"></i>
<i class="bi bi-star-fill text-warning" aria-hidden="true"></i>
💡 Consejo: Busca iconos en icons.getbootstrap.com. Cada icono tiene su clase (
bi-nombre); muchos tienen variante rellena (-fill).
5.2 Como SVG
La fuente carga todos los iconos aunque uses cinco. Con SVG solo incluyes los que necesitas, y además puedes animarlos o estilarlos con más detalle.
<!-- SVG copiado de la web de Bootstrap Icons (botón "Copy HTML") -->
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor"
class="bi bi-check-circle-fill text-success" viewBox="0 0 16 16" aria-hidden="true">
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0m-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/>
</svg>
Si instalas los iconos con npm (npm install bootstrap-icons), puedes usar un sprite: un único fichero SVG con todos los iconos, del que eliges uno con <use>:
<svg class="bi" width="32" height="32" fill="currentColor" aria-hidden="true">
<use href="node_modules/bootstrap-icons/bootstrap-icons.svg#heart-fill"></use>
</svg>
| Método | Ventajas | Inconvenientes |
|---|---|---|
| Fuente (CDN) | Facilísimo; tamaño y color con clases de texto | Descarga todos los iconos |
| SVG en línea | Solo los que usas, sin peticiones extra | HTML más largo |
| Sprite SVG | Un fichero cacheable, HTML limpio | Requiere servir el fichero sprite |
💡 Consejo:
fill="currentColor"hace que el SVG tome el color del texto, así quetext-dangerolink-primaryfuncionan igual que con la fuente.
6. Personalización con Sass
6.1 ¿Por qué Sass?
Bootstrap está escrito en Sass, un lenguaje que se compila a CSS. Compilarlo tú mismo te permite:
- Cambiar los colores de marca en todos los componentes a la vez (botones, alertas, badges, enlaces…).
- Modificar opciones globales: radios, sombras, degradados, espaciados, breakpoints.
- Añadir colores nuevos que generen clases (
btn-marca,text-bg-marca…). - Incluir solo las partes que uses y reducir el tamaño del CSS final.
6.2 Preparar el proyecto
mkdir web-a-medida
cd web-a-medida
npm init -y
npm install bootstrap@5.3.8
npm install --save-dev sass
Estructura recomendada:
web-a-medida/
├── node_modules/
├── scss/
│ └── estilos.scss ← tu fichero Sass
├── css/
│ └── estilos.css ← lo genera el compilador (no lo edites)
├── index.html
└── package.json
Añade estos scripts a package.json:
{
"scripts": {
"css": "sass --load-path=node_modules --quiet-deps --style=compressed scss/estilos.scss css/estilos.css",
"css:watch": "sass --load-path=node_modules --quiet-deps --watch scss/estilos.scss css/estilos.css"
}
}
| Opción | Significado |
|---|---|
--load-path=node_modules | Permite escribir @import "bootstrap/scss/..." sin rutas relativas |
--quiet-deps | Oculta los avisos que vienen de dependencias (Bootstrap) |
--style=compressed | CSS minificado para producción |
--watch | Recompila automáticamente al guardar |
npm run css:watch # mientras desarrollas
npm run css # versión final minificada
⚠️ Importante: Bootstrap 5.3 usa la regla
@importde Sass, que Dart Sass ha marcado como obsoleta. Al compilar verás avisos de deprecación (deprecation warnings). La documentación oficial indica que no impiden la compilación y que pueden ignorarse;--quiet-depsoculta los que vienen de Bootstrap. Son avisos, no errores.
6.3 Opción A: todo Bootstrap con tus variables
// scss/estilos.scss
// 1. Tus variables ANTES de importar Bootstrap
$primary: #6f42c1;
$secondary: #6c757d;
$success: #2a9d8f;
$danger: #e63946;
$font-family-sans-serif: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
$border-radius: 0.75rem;
$border-radius-lg: 1rem;
$enable-shadows: true;
$enable-gradients: false;
$link-decoration: none;
// 2. Bootstrap completo
@import "bootstrap/scss/bootstrap";
// 3. Tus estilos propios, que ya pueden usar variables y mixins de Bootstrap
.hero {
padding-block: $spacer * 4;
background: $primary;
color: $white;
@include media-breakpoint-up(lg) {
padding-block: $spacer * 6;
}
}
📘 Concepto: Las variables de Bootstrap están definidas con
!default, que significa “usa este valor solo si no se ha definido antes”. Por eso tus variables deben ir antes del@import: cuando Bootstrap llega, respeta tu valor.
Enlaza el CSS resultante en lugar del CDN:
<link rel="stylesheet" href="css/estilos.css">
<script src="node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
Variables muy útiles:
| Variable | Valor por defecto | Efecto |
|---|---|---|
$primary, $success… | Colores del tema | Colores de todos los componentes |
$font-family-sans-serif | Pila de fuentes del sistema | Fuente principal |
$font-size-base | 1rem | Tamaño base |
$spacer | 1rem | Base de la escala de espaciado (m-3 = $spacer) |
$border-radius | 0.375rem | Radio por defecto |
$enable-shadows | false | Sombras en botones, inputs… |
$enable-gradients | false | Degradados sutiles en componentes |
$enable-dark-mode | true | Generar el CSS del modo oscuro |
$grid-breakpoints | sm 576px ... xxl 1400px | Breakpoints |
$container-max-widths | 540px ... 1320px | Anchos de .container |
6.4 Mapas: $theme-colors
Los colores del tema se guardan en un mapa de Sass (una lista de pares clave-valor). De ese mapa salen las clases btn-*, bg-*, text-*, alert-*, border-*… Si añades un color al mapa, Bootstrap genera todas sus clases.
Para modificar mapas necesitas la importación por partes (opción B), porque el mapa debe existir antes de modificarlo:
// scss/estilos.scss
// 1. Funciones (necesarias para manipular colores)
@import "bootstrap/scss/functions";
// 2. Tus variables
$primary: #6f42c1;
// 3. Variables de Bootstrap
@import "bootstrap/scss/variables";
@import "bootstrap/scss/variables-dark";
// 4. Modificar mapas: añadir colores y quitar los que no usas
$colores-propios: (
"marca": #ff6b35,
"oceano": #0077b6
);
$theme-colors: map-merge($theme-colors, $colores-propios);
$theme-colors: map-remove($theme-colors, "info", "light");
// 5. Resto de Bootstrap (functions y variables se vuelven a leer, pero no generan
// CSS y, gracias a !default, respetan los valores y mapas que ya has definido)
@import "bootstrap/scss/bootstrap";
<!-- Ahora existen estas clases -->
<button class="btn btn-marca">Botón marca</button>
<button class="btn btn-outline-oceano">Contorno océano</button>
<span class="badge text-bg-oceano">Océano</span>
<p class="text-marca">Texto con el color de marca</p>
⚠️ Importante: En Bootstrap 5.3, las alertas, las variantes de list group y las utilidades
*-subtle/*-emphasisno usan$theme-colorsdirectamente, sino mapas adicionales ($theme-colors-text,$theme-colors-bg-subtle,$theme-colors-border-subtley sus equivalentes-dark). Si quieresalert-marca, tendrás que añadir tu color también a esos mapas; si no, la clase se genera pero sin colores.
6.5 Opción B: importación parcial para reducir tamaño
Si solo usas la rejilla, las utilidades, botones, navbar y cards, ¿para qué cargar carruseles, toasts o popovers?
// scss/estilos.scss — Bootstrap "a la carta"
// 1. Configuración (obligatoria, no genera CSS)
@import "bootstrap/scss/functions";
$primary: #6f42c1; // tus variables aquí
@import "bootstrap/scss/variables";
@import "bootstrap/scss/variables-dark";
@import "bootstrap/scss/maps";
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/utilities";
// 2. Base
@import "bootstrap/scss/root";
@import "bootstrap/scss/reboot";
@import "bootstrap/scss/type";
@import "bootstrap/scss/images";
@import "bootstrap/scss/containers";
@import "bootstrap/scss/grid";
@import "bootstrap/scss/helpers";
// 3. Solo los componentes que uso
@import "bootstrap/scss/buttons";
@import "bootstrap/scss/transitions";
@import "bootstrap/scss/nav";
@import "bootstrap/scss/navbar";
@import "bootstrap/scss/card";
@import "bootstrap/scss/forms";
// 4. API de utilidades AL FINAL (genera las clases m-*, d-*, text-*...)
@import "bootstrap/scss/utilities/api";
// 5. Tu código
| Grupo | Ficheros | ¿Genera CSS? |
|---|---|---|
| Configuración | functions, variables, variables-dark, maps, mixins, utilities | No (solo definiciones) |
| Base | root, reboot, type, images, containers, grid, helpers | Sí |
| Componentes | buttons, navbar, card, modal, carousel… | Sí |
| Utilidades | utilities/api | Sí |
💡 Consejo: Algunos componentes dependen de otros: la navbar usa
navytransitions(para el collapse), los dropdowns necesitandropdown… Si algo se ve roto tras una importación parcial, revisa ennode_modules/bootstrap/scss/bootstrap.scssel orden completo y qué te falta.
💡 Consejo: El JavaScript también se puede reducir: en vez del bundle completo, puedes importar solo los plugins que necesites con un empaquetador (por ejemplo Vite) usando
import Modal from 'bootstrap/js/dist/modal';.
7. La API de utilidades
📘 Concepto: Todas las utilidades de Bootstrap (
m-*,d-*,text-*,opacity-*…) se generan a partir de un mapa Sass llamado$utilities. Si añades una entrada a ese mapa, Bootstrap genera las clases correspondientes, incluidas sus versiones responsive.
7.1 Crear una utilidad nueva
@import "bootstrap/scss/functions";
@import "bootstrap/scss/variables";
@import "bootstrap/scss/variables-dark";
@import "bootstrap/scss/maps";
@import "bootstrap/scss/mixins";
@import "bootstrap/scss/utilities";
// Añadimos utilidades ANTES de utilities/api
$utilities: map-merge(
$utilities,
(
// Genera: .cursor-auto, .cursor-pointer, .cursor-grab y versiones .cursor-md-pointer...
"cursor": (
property: cursor,
class: cursor,
responsive: true,
values: auto pointer grab not-allowed,
),
// Genera: .ratio-img-1x1, .ratio-img-4x3, .ratio-img-16x9
"aspect-ratio": (
property: aspect-ratio,
class: ratio-img,
values: (
"1x1": 1,
"4x3": calc(4 / 3),
"16x9": calc(16 / 9),
),
),
)
);
// Resto de Bootstrap: incluye utilities/api, que genera también tus clases nuevas
@import "bootstrap/scss/bootstrap";
<div class="card cursor-pointer">Tarjeta clicable</div>
<img src="foto.jpg" class="w-100 ratio-img-4x3 object-fit-cover" alt="Foto recortada a 4:3">
| Opción | Para qué |
|---|---|
property | Propiedad CSS (o lista de propiedades) |
class | Prefijo de la clase generada |
values | Lista (a b c) o mapa ("nombre": valor) |
responsive: true | Genera también -sm-, -md-, -lg-… |
print: true | Genera la versión -print- |
state: hover | Genera variantes de estado como .opacity-50-hover |
7.2 Eliminar utilidades que no usas
// Quitar las utilidades de ancho/alto si no las usas (reduce el CSS)
$utilities: map-merge(
$utilities,
(
"width": null,
"height": null,
)
);
8. Accesibilidad en Bootstrap
Bootstrap ofrece una buena base, pero la accesibilidad final depende de ti: del HTML, del contenido y de tus personalizaciones.
| Buena práctica | Cómo con Bootstrap |
|---|---|
| Enlace “saltar al contenido” | <a class="visually-hidden-focusable" href="#contenido">Saltar al contenido</a> |
| Texto solo para lectores de pantalla | visually-hidden |
| Foco visible | No elimines el outline; puedes usar la utilidad focus-ring en elementos propios |
| Contraste suficiente | Comprueba tus colores personalizados (mínimo 4.5:1 en texto normal) |
| Movimiento reducido | Bootstrap reduce las transiciones con prefers-reduced-motion; respétalo en tu CSS |
| Atributos ARIA de los componentes | Cópialos de la documentación y actualízalos (aria-current, aria-expanded…) |
| Iconos | aria-hidden="true" si son decorativos, aria-label si transmiten información |
| Formularios | Siempre <label for>; mensajes con invalid-feedback y aria-describedby |
<body>
<a class="visually-hidden-focusable" href="#contenido">Saltar al contenido principal</a>
<nav class="navbar">...</nav>
<main id="contenido">...</main>
</body>
⚠️ Importante: Al cambiar
$primaryu otros colores, Bootstrap calcula el color de texto de botones y badges según$min-contrast-ratio(4.5 por defecto), pero no comprueba todas las combinaciones. Verifica tus colores con el panel de accesibilidad de DevTools o con Lighthouse.
9. Buenas prácticas y cuándo NO usar Bootstrap
9.1 Buenas prácticas
- Fija la versión (
bootstrap@5.3.8), nunca uses “latest” en producción. - Usa las utilidades para ajustes de maquetación y clases propias para estilos de marca.
- No mezcles HTML de Bootstrap 4 y 5: revisa siempre la versión de los ejemplos que copies.
- Personaliza: cambia al menos colores, tipografía y radios para que tu web no “huela a Bootstrap”.
- No cargues el JS si no usas componentes interactivos; y en producción, valora compilar solo lo necesario.
- Respeta la estructura de los componentes y sus atributos ARIA.
- No abuses de las utilidades: si repites la misma combinación de 10 clases en 20 sitios, crea una clase o un componente.
<!-- ❌ Difícil de mantener si se repite en muchas páginas -->
<a class="btn btn-primary btn-lg rounded-pill px-5 py-3 shadow fw-bold text-uppercase">Comprar</a>
<!-- ✅ Una clase con significado -->
<a class="btn btn-primary btn-cta">Comprar</a>
9.2 Cuándo NO usar Bootstrap
| Situación | Por qué | Alternativa |
|---|---|---|
| Diseño muy personalizado o de marca fuerte | Pasarás más tiempo deshaciendo estilos que creándolos | CSS propio con Grid, Flex y variables |
| Una landing muy pequeña y rápida | El CSS de Bootstrap pesa más que toda tu página | CSS a mano |
| Proyecto con un sistema de diseño propio | Ya tienes tus componentes | Tu librería de componentes |
| Equipo que trabaja con utility-first | Otra filosofía | Tailwind CSS |
| Aprender CSS | Te oculta cómo funcionan las cosas | Primero CSS puro (como en este curso) |
10. Comparación breve con Tailwind CSS
Tailwind CSS es otro framework muy popular, con un enfoque distinto: utility-first. No trae componentes hechos, sino miles de utilidades de bajo nivel con las que construyes todo.
<!-- Bootstrap: componente ya diseñado -->
<button class="btn btn-primary">Guardar</button>
<!-- Tailwind: el diseño se compone con utilidades -->
<button class="rounded-lg bg-blue-600 px-4 py-2 font-semibold text-white hover:bg-blue-700 focus-visible:outline-2">
Guardar
</button>
| Aspecto | Bootstrap 5.3 | Tailwind CSS 4 |
|---|---|---|
| Filosofía | Componentes + utilidades | Solo utilidades (utility-first) |
| Componentes listos | Sí (navbar, modal, carrusel…) | No (hay librerías aparte, gratuitas y de pago) |
| JavaScript incluido | Sí, para componentes interactivos | No |
| Instalación | CDN o npm, sin compilar | Requiere un paso de compilación (CLI, Vite…) |
| Personalización | Variables CSS y Sass | Configuración en CSS (@theme) |
| Tamaño final | Fijo, salvo importación parcial | Solo genera las clases que usas |
| Aspecto por defecto | Reconocible si no se personaliza | No tiene aspecto propio |
| Curva de aprendizaje | Suave para empezar | Hay que conocer bien CSS |
💡 Consejo: No hay un ganador absoluto. Bootstrap es imbatible para tener rápido una interfaz completa y coherente; Tailwind brilla en diseños a medida dentro de proyectos con herramientas de construcción. Lo que aprendiste de CSS te sirve para ambos.
11. Ejercicios
Ejercicio 1: Sobrescribir con criterio
Crea una página con una navbar, tres cards y botones. Sin modificar las clases de Bootstrap, crea tus propias clases (.card-servicio, .btn-cta) para dar un estilo distinto. Usa DevTools para comprobar qué reglas ganan y por qué.
Ejercicio 2: Tema con variables CSS
Usando solo variables CSS (con Bootstrap por CDN), cambia la fuente a una de Google Fonts, el radio global, el color de los enlaces y el color de .btn-primary con todos sus estados (hover, active, focus). Comprueba el resultado navegando con el teclado.
Ejercicio 3: Modo oscuro completo
Añade al ejercicio anterior el conmutador claro/oscuro/automático de la sección 4. Revisa la página en modo oscuro y sustituye cualquier clase fija (bg-white, text-dark, bg-light) por su equivalente semántico. Personaliza --bs-body-bg del modo oscuro.
Ejercicio 4: Iconos accesibles
Crea un pie de página con iconos de redes sociales (fuente de Bootstrap Icons) con aria-label correcto, y una lista de características con iconos SVG en línea decorativos (aria-hidden="true"). Compara el peso de la fuente frente al de los SVG en la pestaña Network.
Ejercicio 5: Bootstrap de marca con Sass
Monta un proyecto con npm y Sass. Define $primary, $secondary, la fuente y $border-radius. Añade dos colores al mapa $theme-colors y crea una página de muestra con botones, badges y textos de todos los colores. Compila con npm run css.
Ejercicio 6: Dieta para Bootstrap
Partiendo del ejercicio 5, cambia a importación parcial incluyendo solo lo necesario para una landing (rejilla, utilidades, botones, navbar, cards). Compara el tamaño de css/estilos.css antes y después.
Ejercicio 7: Utilidades propias
Con la API de utilidades, crea: .cursor-* (responsive), .ratio-img-* y una utilidad .tracking-* para letter-spacing con valores tight (-0.02em), normal (0) y wide (0.1em). Úsalas en una página.
Resumen
| Concepto | Sintaxis / herramienta | Para qué |
|---|---|---|
| CSS propio | Fichero enlazado después de Bootstrap | Ajustes puntuales |
| Clases propias | .card-producto, .btn-cta | Personalizar sin romper lo demás |
| Capas | @import url(...) layer(bootstrap); | Ganar a Bootstrap sin especificidad |
| Variables globales | --bs-body-font-family, --bs-primary, --bs-primary-rgb | Tema en tiempo real |
| Variables de componente | --bs-btn-bg, --bs-navbar-color, --bs-card-spacer-x | Personalizar un componente |
| Modo oscuro | data-bs-theme="dark" | Tema oscuro global o por zonas |
| Conmutador | localStorage + matchMedia | Recordar y seguir la preferencia |
| Clases semánticas | bg-body, bg-body-tertiary, text-body-secondary | Adaptarse al modo de color |
| Bootstrap Icons | <i class="bi bi-nombre">, SVG, sprite | Iconos |
| Sass: variables | $primary: ...; antes de @import | Cambiar valores por defecto (!default) |
| Sass: mapas | map-merge($theme-colors, ...) | Añadir o quitar colores del tema |
| Importación parcial | functions, variables, maps, mixins + lo que uses | Reducir tamaño |
| API de utilidades | $utilities: map-merge($utilities, (...)) | Crear o eliminar utilidades |
| Accesibilidad | visually-hidden-focusable, focus-ring, ARIA, contraste | Webs para todos |
| Tailwind CSS | Utility-first con compilación | Alternativa para diseños a medida |