CSS de cero a experto
La capa visual de la web: selectores sin misterio, especificidad calculada, box model real, flexbox a fondo, grid, responsive moderno con container queries y animaciones — hasta construir una página completa lista para producción.
1 · Qué es CSS y cómo piensa el navegador
Básico ~11 minCSS no «pinta encima» del HTML: lo describe, y el navegador calcula una caja por cada elemento. Entender ese cálculo es la diferencia entre adivinar y saber.
Las tres capas, ahora con nombres de archivo
| Capa (eco HTML cap. 1) | Tecnología | Responde a… |
|---|---|---|
| Estructura | HTML | ¿QUÉ es esta página? |
| Presentación | CSS | ¿CÓMO se ve y se acomoda? |
| Comportamiento | JavaScript | ¿Qué HACE al interactuar? |
Anatomía de una regla
selector {
propiedad: valor;
otra-propiedad: otro-valor;
}
/* ejemplo real: todos los parrafos */
p {
color: #333; /* color del texto */
line-height: 1.6; /* aire entre lineas */
}
- Comentarios SOLO con
/* ... */— las dobles barra (//) de JS NO existen aquí y rompen la declaración. - Cada declaración termina en
;; el último puede omitirlo, pero ponerlo evita bugs al añadir líneas.
Cómo piensa el navegador
- Parsea tu HTML → construye el DOM (árbol, eco js_01).
- Parsea tu CSS → construye el CSSOM: cada regla clasificada por selector y peso.
- Cruza ambos: para CADA nodo resuelve «qué reglas le aplican, cuál gana» — el tema de los caps. 5 y 32.
- Con el estilo final dibuja una caja por elemento (cap. 7) y las acomoda según display/flujo (cap. 13+).
Tu página YA tiene CSS: el user agent stylesheet
Si un <h1> sale grande y negro sin que escribas nada, es porque
el navegador trae su propia hoja base. Todo lo que hagas es REEMPLAZAR esa hoja.
En DevTools (F12 → Styles) la verás tachada cuando tu regla la vence:
<!-- sin una linea de CSS propio... -->
<h1>Soy grande porque el navegador dice h1 { font-size: 2em; }</h1>
Tres formas de aplicar CSS (y su veredicto)
| Forma | Ejemplo | Veredicto |
|---|---|---|
| inline (atributo style) | <p style="color:red"> | solo para pruebas puntuales: gana a todo (cap. 5), imposible de reutilizar |
| <style> embebido | bloque en el head | válido para CSS crítico mínimo (eco HTML cap. 26); no escala |
| hoja externa | <link rel="stylesheet"> | LA forma: cacheable, reusable, separación de capas |
Puntos clave
- CSS describe cajas; el navegador las calcula y pinta.
- Solo /* */ para comentar; punto y coma siempre.
- DOM + CSSOM → estilos por nodo → cajas → layout.
- Hoja externa como regla; inline solo pruebas.
2 · Tu primera hoja de estilos
Básico ~10 minUn proyecto de tres archivos y el flujo de trabajo que usarás siempre: HTML, CSS y servidor local.
Estructura del proyecto
$ mkdir mi-tienda && cd mi-tienda
$ touch index.html estilos.css
# mismo habito del manual de HTML: npx serve .
$ npx serve .
# → http://localhost:3000
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mi tienda</title>
<link rel="stylesheet" href="estilos.css">
</head>
<body>
<header><h1>Mi tienda</h1></header>
<main>
<p class="destacado">Oferta del dia: café Chanchamayo.</p>
</main>
</body>
</html>
/* estilos.css */
body {
font-family: system-ui, sans-serif;
margin: 0; /* el body trae margin por defecto: fuera */
}
header {
background: #663399;
color: #fff;
padding: 1rem;
}
.destacado {
border-left: 4px solid #472272;
padding-left: .75rem;
font-weight: bold;
}
Detalles que ahorran horas de confusión
- Ruta relativa:
href="estilos.css"busca JUNTO al HTML. Si moviste el archivo a/css/, actualiza la ruta:href="css/estilos.css". Error 404 en la hoja = página «desnuda» sin mensaje de error visible — revisa SIEMPRE la pestaña Network. - Tolerancia a errores: una declaración inválida se IGNORA y la hoja sigue viviendo. Tu color no aplica pero el resto sí — depura línea por línea.
- El cache muerde: cambias el CSS y no ves cambios… es la copia guardada. Recarga con Ctrl+F5 o desactiva caché en DevTools (Network → Disable cache).
Convención de la casa
| Regla | Ejemplo |
|---|---|
| minúsculas y guiones en clases | .tarjeta-producto, nunca .TarjetaProducto |
| nombres por QUÉ es, no por CÓMO se ve | .destacado mejor que .borde-verde |
| indentación de 2 espacios | propiedades anidadas visualmente bajo el selector |
| una propiedad por línea | fácil de diffear en git y de leer en reviews |
Prueba de humo: ¿mi CSS llega?
body {
background: #ffe600; /* amarillo imposible de ignorar */
}
- Amarillo visible → tu pipeline HTML→CSS funciona; quita la línea.
- Sigue blanco → ruta equivocada, 404, o estás editando OTRO archivo.
Puntos clave
- Tres archivos: index.html + estilos.css + servidor local.
- 404 silencioso en el link = página desnuda; mira Network.
- CSS ignora declaraciones malas sin avisar; Ctrl+F5 contra caché.
- Nombres semánticos desde el día uno.
3 · Selectores I: básicos y combinadores
Básico ~12 minEl selector decide A QUIÉN le hablas. Dominarlos es dominar la mitad de CSS.
Los cuatro simples
| Selector | Nombre | Cuándo usarlo |
|---|---|---|
p | elemento | estilos base globales del tag |
.tarjeta | clase | el caballo de batalla: reutilizable, sin peso extra (cap. 5) |
#cabecera | id | casi nunca en CSS: único y MUY pesado; para anclas/JS mejor |
* | universal | resets puntuales: * { box-sizing: border-box } (cap. 7) |
Combinadores: las relaciones familiares
<nav class="menu">
<ul>
<li><a href="#">Inicio</a>
<ul>
<li><a href="#">Cafés</a></li>
</ul>
</li>
<li><a href="#">Contacto</a></li>
</ul>
</nav>
| Selector | Lee «…» | Toma en el ejemplo |
|---|---|---|
.menu ul | descendiente (cualquier profundidad) | Ambas listas |
.menu > ul | hijo DIRECTO | Solo la lista exterior |
li + li | hermano inmediato siguiente | los li 2.º en adelante — separadores de menú |
h2 ~ p | hermanos posteriores (todos) | todo párrafo tras un h2 |
/* separador entre items del menu SIN tocar el primero */
.menu > ul > li + li {
border-top: 1px solid #eee;
}
Compuestos y agrupación
p.destacado /* p que ADEMAS tiene clase destacado: SIN espacio */
.tarjeta.oferta /* con ambas clases a la vez */
/* agrupacion: una receta, varios destinatarios */
h1, h2, h3 {
line-height: 1.2;
}
/* selector en lista larga: legible verticalmente */
input[type="text"],
input[type="email"],
textarea {
width: 100%;
}
p.destacado es un
solo elemento con ambas cosas; p .destacado es cualquier .destacado
DENTRO de un p. Error de dedo clásico que cuesta horas.Estrategia anti-divitis (eco HTML cap. 6)
- Clases cortas y semánticas sobre los puntos de variación — no cadenas
de descendencia frágiles tipo
div div div span. - Si tu selector necesita 4 niveles para llegar, el HTML probablemente necesita una clase donde empieza la zona.
- Regla práctica: selectores de ≤2 combinadores en el 90% de los casos.
Puntos clave
- Clases = pan diario; ids casi prohibidos en CSS.
- >, +, ~ dan precisión estructural sin clases extra.
- p.x ≠ p .x: el espacio es un combinador.
- Selectores cortos y estables vencen a cadenas largas.
4 · Selectores II: atributos y pseudoclases
Básico ~13 minEstados, posiciones y atributos sin escribir una línea de JavaScript ni una clase extra en el HTML.
Selectores de atributo
| Selector | Coincide cuando… |
|---|---|
[required] | tiene el atributo, valor da igual |
[type="email"] | valor EXACTO |
[href^="https://"] | EMPIEZA con… (enlaces seguros) |
[href$=".pdf"] | TERMINA con… (aviso de descarga) |
[href*="/cafe"] | CONTIENE… |
/* avisa que un link sale del sitio o baja un pdf */
[href^="https://"]::after { content: " \2197"; } /* flecha ↗ */
a[href$=".pdf"]::after {
content: " (PDF)";
font-size: .8em;
color: #666;
}
Pseudoclases de estado: reaccionar sin JS
:root {
--acento: #663399; /* adelanto cap. 31 */
}
.boton:hover { background: #472272; }
.boton:focus-visible {
outline: 3px solid var(--acento);
outline-offset: 2px; /* eco a11y del manual HTML */
}
.boton:active { transform: scale(.97); }
:hoversolo existe donde hay mouse — el estado táctil lo cubre:active; el teclado,:focus-visible.- Orden recomendado LVHA para enlaces:
:link → :visited → :hover → :focus(la cascada decide empates).
Pseudoclases estructurales: la aritmética de nth
| Selector | Qué toma |
|---|---|
li:first-child / :last-child | primero/último entre sus hermanos |
tr:nth-child(odd) | filas impares — cebra en tablas sin clases |
li:nth-child(3) | exactamente el tercero |
li:nth-child(3n) | cada tercero: 3, 6, 9… |
li:nth-child(an+b) | fórmula general: empieza en b, paso a |
/* cebra accesible: filas alternas sin tocar el PHP que imprime la tabla */
tbody tr:nth-child(odd) {
background: #f6f2fb;
}
/* en flex/grid (caps. 14+): margen solo entre items */
.tarjeta:not(:last-child) {
margin-bottom: 1rem;
}
::before y ::after: hijos decorativos
.obligatorio::after {
content: "*"; /* OBLIGATORIO: sin content no existe */
color: #b23117;
margin-left: .25rem;
}
.cita::before { content: open-quote; }
.cita::after { content: close-quote; }
- Generan contenido DECORATIVO: los lectores de pantalla pueden leerlo, así que nunca pongas información esencial solo ahí (eco cap. 25 HTML).
- Perfectos para iconos, badges y adornos SIN ensuciar el markup.
Puntos clave
- ^= $= *= leen atributos: semántica gratis sobre tu propio HTML.
- hover/focus-visible/active cubren mouse, teclado y toque.
- nth-child(an+b) = cebra y espaciados sin clases ni JS.
- ::before/::after exigen content; decoración, no datos vitales.
5 · Especificidad y cascada a fondo
Básico ~14 minDos reglas apuntan al mismo elemento: ¿cuál gana? Esta es la pregunta que separa a quien adivina de quien sabe CSS.
La cascada: el orden de desempate completo
- Origen e importancia: !important del usuario > !important del autor > estilos normales del autor (los tuyos).
- Especificidad: más específico gana.
- Orden de aparición: empate total → gana la ÚLTIMA regla del archivo.
Especificidad se CALCULA, no se intuye
Toda selector vale una tripleta (A, B, C):
A = ids · B = clases/atributos/pseudoclases · C = elementos/pseudoelementos.
Se compara de izquierda a derecha; no hay «carry».
| Selector | (A,B,C) |
|---|---|
p | (0,0,1) |
.tarjeta | (0,1,0) |
p.tarjeta | (0,1,1) |
.menu li a:hover | (0,2,2) ← :hover cuenta en B |
#promo .btn | (1,1,0) |
| style="..." inline | (1,0,0,0): vence a TODO selector |
p { color: gray; } /* (0,0,1) */
.aviso { color: orange; } /* (0,1,0) GANA aunque venga antes */
p.aviso { color: red; } /* (0,1,1) gana a las dos anteriores */
La trampa clásica del id
#cabecera .titulo { color: navy; } /* (1,1,0) */
.titulo.nuevo { color: purple; } /* (0,2,0) ¡NUNCA ganara! */
- Un solo id pesa más que MIL clases. Si maquetas con ids, luego no puedes sobreescribir sin otro id o !important — deuda técnica instantánea.
- Regla de la casa: cero ids en CSS; ids para anclas (#cap5) y getElementById, nada más.
!important: extintor, no perfume
.oculto { display: none !important; } /* utilidad legitima de emergencia */
- Válido para utilidades globales (.oculto, .sr-only) y para vencer estilos inline de terceros.
- Cada !important añadido «para arreglar» un estilo te obliga al siguiente !important — espiral conocida como guerra de importancias. Si necesitas dos, el problema es de especificidad mal diseñada, no de fuerza bruta.
Depurar la cascada en 30 segundos
- F12 → clic derecho al elemento → Inspect.
- Panel Styles: verás TODAS las reglas candidatas; las perdedoras TACHADAS.
- Pasa el mouse sobre el nombre del selector: DevTools muestra SU tripleta.
- ¿Tu regla ni aparece? El SELECTOR no coincide (cap. 3–4), no es cascada.
@layer para declarar jerarquías explícitas — lo vemos en el
capítulo 32. Primero domina la tripleta: las capas se apoyan en ella.Puntos clave
- Desempate: origen/importancia → especificidad → orden.
- Tripleta (ids, clases+pseudo, elementos); sin carry.
- Cero ids en tus hojas; inline vence a todo selector.
- !important solo utilidades globales; Styles panel para depurar.
6 · Herencia, unidades y valores
Básico ~13 minPor qué el hijo hereda la letra pero no el borde, qué unidad usar en cada caso y cómo piensa calc().
Herencia: lo tipográfico baja, lo visual no
<body> <!-- color:#333; font-family:... -->
<p>Texto hereda color y fuente.</p>
<div style="border:1px solid">
<p>NO hereda el borde del div padre.</p>
</div>
</body>
| SÍ se hereda | NO se hereda |
|---|---|
| color, font-*, line-height, text-align | border, margin, padding, background |
| visibility, cursor | width/height, display, position |
- Palabras clave universales:
inherit(fuerza heredar),initial(valor base),unset(hereda si es heredable, si no initial),revert(vuelve al user agent). - Ejemplo útil:
input, button { font: inherit }— los form controls NO heredan por defecto; esta línea arregla la inconsistencia típica.
Unidades: la tabla de decisión
| Unidad | Relativa a… | Úsala para… |
|---|---|---|
px | nada: fijo | bordes, sombras, detalles de 1–3 px |
em | font-size DEL PROPIO elemento | padding/margins escalables dentro de texto (¡cuidado: se acumula!) |
rem | font-size de la raíz (16px default) | TODO lo demás: espaciados, tamaños consistentes |
% | contenedor padre | anchos fluidos simples |
vw / vh | viewport completo | heros full-screen; ojo móvil con vh (100dvh moderno) |
ch | ancho del «0» | medidas de texto: max-width de párrafos ≈ 60ch |
html { font-size: 100%; } /* 16px: base respetando usuario */
p { max-width: 60ch; } /* linea legible, ni trena ni corta */
.tarjeta { padding: 1rem 1.25rem; } /* escala SI el usuario sube su fuente */
.hero { min-height: 100dvh; } /* alto real en moviles */
calc(): matemática entre unidades
aside {
width: calc(100% - 18rem); /* % menos rem: imposible a mano */
float: right;
}
- Espacios OBLIGATORIOS alrededor de + y − (
calc(100%-18rem)es inválido: el parser ve «100%» pegado al signo). - Con flexbox/grid (caps. 14+) necesitarás calc() cada vez menos — gap y fr resuelven — pero sigue siendo la herramienta para mezclas raras.
Accesibilidad incluida gratis
Todo en rem hace que subir el tamaño de fuente del navegador (preferencia de baja visión) escale tu diseño entero. Los px fijos ignoran esa preferencia: es la diferencia entre «accesible» y «se rompe al hacer zoom».
Puntos clave
- Tipografía hereda; cajas no. font: inherit para inputs.
- rem por defecto; em solo dentro de contexto textual.
- ch para medidas de lectura; dvh para altos móviles.
- calc() con espacios; fr/gap reemplazan muchos cálculos.
7 · Box model a fondo: content-box vs border-box
Intermedio ~13 minCada elemento es una caja de cuatro capas. El 90% de los «CSS me traicionó» nace de no saber qué mide cada capa.
El mapa de la caja
/* margin -> afuera, transparente
border -> el marco
padding -> colchon interno
content -> texto o imagen */
.tarjeta {
width: 300px;
padding: 20px;
border: 5px solid #663399;
}
La pregunta del millón: ¿300px de QUÉ?
| box-sizing | width mide… | Ancho real de .tarjeta |
|---|---|---|
| content-box (default histórico) | solo el contenido | 300 + 40 + 10 = 350px: «¡puse 300 y se agrandó!» |
| border-box | bordes incluidos | 300px exactos; el padding come adentro |
La primera línea de TODO proyecto real
*,
*::before,
*::after {
box-sizing: border-box; /* TODA caja mide lo que declaras */
}
body { margin: 0; } /* el user agent pone margin:8px */
- Con border-box, cambiar padding NUNCA rompe tu grilla: el elemento mantiene su tamaño prometido.
- ::before y ::after también son cajas — sin el reset, un pseudo-elemento con width:100% desbordaría dentro de su padre.
DevTools te dibuja la caja
- F12 → clic derecho al elemento → Inspect.
- Bajo el panel Styles: diagrama concéntrico margin/border/padding/content con los números REALES calculados.
- Pasa el mouse sobre cada anillo: el navegador lo resalta en la página.
width fijo vs max-width fluido
.contenedor {
max-width: 72rem; /* crece hasta ahi y para */
margin-inline: auto; /* centrado horizontal moderno */
padding-inline: 1rem;
}
img {
max-width: 100%; /* nunca desborda su caja */
height: auto; /* proporcion intacta */
}
- width fijo es frágil ante pantallas chicas; max-width es la base del responsive — Parte V entera construye sobre esto.
- height fija rompe cuando el texto crece o el usuario sube su fuente: usa min-height cuando necesites un mínimo.
Puntos clave
- Cuatro capas: margin fuera → border → padding → content.
- border-box global = width honesto; línea uno de todo proyecto.
- max-width + margin auto: contenedor centrado clásico.
- DevTools dibuja las cuatro capas con números reales.
8 · Margin collapse y espaciados
Intermedio ~12 minDos margins de 20px juntos NO dan 40. El colapso vertical es el comportamiento más contraintuitivo de CSS — y más fácil de dominar.
La regla: en vertical, los margins se FUSIONAN
<p style="margin-bottom:20px">Parrafo A</p>
<p style="margin-top:30px">Parrafo B</p>
Entre A y B hay 30px, no 50: el mayor se come al menor. Solo pasa EN VERTICAL (top/bottom) y solo entre cajas de bloque en flujo normal.
Los tres escenarios del colapso
| Caso | Qué pasa |
|---|---|
| hermanos | bottom de uno + top del otro → gana el mayor |
| padre-hijo | el margin-top del PRIMER hijo «se escapa» y empuja al PADRE entero (¡clásico!) |
| caja vacía | un div sin contenido ni border colapsa sus propios top/bottom entre sí |
/* el escape del padre: quieres separar el h1 del header... */
header { background: #663399; }
header h1:first-child { margin-top: 2rem; } /* ¡el header se mueve! */
Cómo ROMPER un colapso cuando molesta
| Solución | Ejemplo | Nota |
|---|---|---|
| padding en vez de margin | padre con padding-top | el padding NUNCA colapsa |
| interponer algo | border o padding mínimo en el padre | aunque sea 1px transparente |
| overflow distinto de visible | overflow: clip en el padre | mínima invasión moderna |
| cambiar de contexto | flex/grid en el padre | los hijos flex/grid JAMÁS colapsan — razón extra para las Partes III–IV |
gap: la solución moderna a espaciados
.lista {
display: flex;
flex-direction: column;
gap: 1rem; /* espacio UNIFORME, sin trampas de margen */
}
- Margins entre items exigen hacks (
:not(:last-child), cap. 4); gap lo declara una vez y no colapsa jamás. - gap funciona en flex, grid y multicolumna — es LA herramienta de espaciado estructural desde 2021.
Estrategia de espaciado de la casa
- Dentro de un componente: padding (no colapsa, respeta background).
- Entre hermanos dentro de un contenedor controlado: gap si es flex/grid; margins solo en flujo de texto normal (párrafos).
- Escalas coherentes: usa múltiplos de tu base rem: .25 / .5 / 1 / 2 / 4rem. La consistencia visual nace de repetir pocas medidas.
Puntos clave
- Colapso solo vertical, solo bloques, solo flujo normal.
- El caso padre-hijo «escapa» y mueve al contenedor entero.
- Rupturas: padding, overflow:clip, o pasar a flex/grid.
- gap para espaciado estructural; padding hacia adentro.
9 · Colores y gradientes
Intermedio ~12 minCuatro sintaxis, una recomendada para trabajar, y gradientes que sustituyen imágenes pesadas.
Las sintaxis y para qué sirve cada una
| Formato | Ejemplo | Fortaleza |
|---|---|---|
| hex | #663399 | compacto; el estándar de los design tokens |
| rgb | rgb(102 51 153 / .8) | familiar desde JS (eco js_01) |
| hsl | hsl(270 50% 40%) | razonable por humanos: tono, saturación, luminosidad |
| nombrado | rebeccapurple | prototipos rápidos |
:root {
--marca: hsl(270 50% 40%); /* base */
--marca-suave: hsl(270 50% 92%);/* misma familia: SOLO cambia L */
--marca-hover: hsl(270 50% 30%);/* oscurecer = bajar L */
}
/* alpha moderno con barra: 4to canal */
.sombra-texto { color: rgb(0 0 0 / .6); }
opacity vs alpha: NO son lo mismo
.tarjeta { opacity: .8; } /* TODO el elemento al 80%:
texto incluido */
.tarjeta { background: rgb(102 51 153 / .8); } /* solo el fondo
translucido */
Gradientes: imágenes sin descargar nada
.hero {
/* banner de marca: la tecnica de esta plantilla misma */
background: linear-gradient(90deg, #663399, #472272);
}
.fondo-pagina {
background:
radial-gradient(60rem 22rem at 85% -10%, rgba(102,51,153,.16), transparent 60%);
}
.cono-decorativo {
background: conic-gradient(from 180deg, #663399, #b23117, #663399);
border-radius: 50%;
}
- linear: banda recta — botones, banners, overlays sobre fotos.
- radial: foco circular — fondos con profundidad (la plantilla del manual usa dos).
- conic: barrido angular — donuts decorativos, relojes, loaders.
- Son IMÁGENES generadas: aceptan background-size/repeat como cualquier img.
currentColor: un color que se propaga solo
.boton-fantasma {
color: #663399; /* texto morado */
border: 2px solid currentColor; /* borde SIEMPRE igual al texto */
}
.boton-fantasma:hover {
color: #472272; /* borde acompana automaticamente: un solo cambio */
}
Es el mismo truco de los SVG inline del manual de HTML (cap. 21): un solo valor gobierna texto, borde e icono a la vez.
Puntos clave
- HSL para familias de marca: mueves la L, conservas identidad.
- Alpha en el color cuando quieres translucidez parcial.
- Gradientes = cero peticiones; combinables con capas.
- currentColor sincroniza borde/icono/texto.
10 · Tipografía a fondo
Intermedio ~14 minEl 95% de la web es texto. Tipografía bien resuelta es la mejora visual más barata que existe.
font-family: la pila defensiva
body {
font-family: system-ui, -apple-system, "Segoe UI", Roboto,
sans-serif; /* generica SIEMPRE al final */
}
code, pre {
font-family: ui-monospace, "Cascadia Code", Consolas, monospace;
}
- El navegador prueba en orden y usa la primera disponible; la genérica (sans-serif/monospace/serif) garantiza un resultado digno.
- Nombres con espacios entre comillas.
system-ui= la fuente nativa del SO: cero descargas, look nativo — la favorita para body.
Web fonts: bonitas pero con costo
<!-- eco rendimiento HTML cap. 26: conexion temprana -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap">
| font-display | Comportamiento |
|---|---|
| swap | muestra la fallback YA y cambia al cargar: recomendado |
| block | texto invisible hasta 3s: el «FOIT» que castiga LCP |
| optional | solo si ya estaba en caché: máxima velocidad, posible salto |
Escala tipográfica en rem
:root {
--fs-base: 1rem; /* 16px */
--fs-sm: .875rem;
--fs-lg: 1.25rem;
--fs-xl: clamp(1.6rem, 4vw + .5rem, 2.2rem); /* adelanto cap. 25 */
}
body { font-size: var(--fs-base); line-height: 1.65; }
h1 { font-size: var(--fs-xl); line-height: 1.15; }
small { font-size: var(--fs-sm); }
- line-height SIN unidad (1.65) se multiplica por cada fuente: escalas bien en todos los tamaños. Con px queda rígido.
- Regla clásica de legibilidad: líneas de 45–75 caracteres →
max-width: 60ch(cap. 6). - Títulos más apretados (1.1–1.2), cuerpo aireado (1.6–1.8).
Peso, estilo y variantes modernas
| Propiedad | Uso honesto |
|---|---|
font-weight | 400 normal · 600/700 énfasis; NO uses bold como decoración general |
letter-spacing | títulos grandes ligeramente negativo (-.02em); MAYÚSCULAS pequeñas positivo (.08em) |
text-transform | mayúsculas visuales sin cambiar el HTML real (los lectores leen el original) |
| fuente variable | un archivo, todos los pesos: font-weight: 550 válido |
Puntos clave
- Pila con genérica final; system-ui gratis y nativo.
- display=swap siempre; preconnect al CDN de fuentes.
- Escala rem + line-height unitless + max-width ch.
- 2 familias / 4 pesos máximo.
11 · Texto y decoración
Intermedio ~12 minAlineación, truncado elegante y listas con personalidad — las herramientas del día a día.
Alinear y transformar
.encabezado-tarjeta { text-align: center; }
.precio { text-align: end; } /* fisico: right en LTR,
se invierte en arabe */
.etiqueta {
text-transform: uppercase;
letter-spacing: .08em; /* mayusculas piden aire entre letras */
font-size: .75rem;
}
- Valores lógicos (
start/end) sobre físicos (left/right): gratis compatibles con idiomas RTL (eco HTML cap. 28).
Subrayados que no parecen de 1998
a {
text-decoration-color: rgb(102 51 153 / .4);
text-decoration-thickness: from-font;
text-underline-offset: 3px;
}
a:hover { text-decoration-thickness: 2px; }
Truncado: una línea vs varias
<p class="una-linea">Título larguísimo que debe cortarse…</p>
<p class="dos-lineas">Resumen que puede ocupar hasta dos
renglones antes del puntos suspensivos.</p>
/* una linea: la clasica elipsis */
.una-linea {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* N lineas: line-clamp (necesita prefijo webkit, ya estandar) */
.dos-lineas {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
- Perfectos para tarjetas de producto con títulos desiguales: todas las cards quedan a la misma altura visual.
- Añade SIEMPRE un title o un «ver más» accesible: el texto cortado no debe perderse para teclado ni lectores.
Listas con identidad
ul.beneficios {
list-style: none;
padding-left: 0; /* el user agent pone 40px */
}
.beneficios li::before {
content: "\2713"; /* ✓ via unicode */
color: #663399;
margin-right: .5rem;
font-weight: bold;
}
/* ::marker: estiliza SIN pseudo-elemento */
ol.pasos > li::marker {
color: #b23117;
font-weight: bold;
}
Espacios en blanco y desborde controlado
| white-space | Efecto |
|---|---|
normal | colapsa espacios y salta línea (default) |
nowrap | sin saltos de línea: base del truncado |
pre | respeta TODO como lo escribiste (los pre del manual) |
pre {
overflow-x: auto; /* scroll horizontal solo si hace falta,
sin romper el layout movil */
}
Puntos clave
- start/end antes que left/right: RTL gratis.
- nowrap+ellipsis para 1 línea; line-clamp para N.
- ::marker y ::before personalizan listas sin imágenes.
- overflow-x auto en bloques anchos: móvil protegido.
12 · Bordes, radios y sombras
Intermedio ~12 minLas tres herramientas que convierten un bloque plano en una tarjeta con profundidad.
border: shorthand y lados
.tarjeta {
border: 1px solid #e5e0ee; /* ancho estilo color */
}
.subrayado {
border-bottom: 3px solid #663399; /* solo un lado */
}
/* lados logicos: respetan idiomas RTL */
.etiqueta { border-inline-start: 4px solid #b23117; }
border-radius: de esquinas a círculos
.avatar { border-radius: 50%; } /* circulito perfecto */
.boton { border-radius: .5rem; }
.pildora { border-radius: 999px; } /* pill button clasica */
/* esquina por esquina: TL TR BR BL */
.foto { border-radius: 1rem 1rem 0 0; } /* solo arriba */
box-shadow: la sintaxis completa
/* x y blur spread color */
.sombra-suave { box-shadow: 0 .5rem 1.5rem rgb(0 0 0 / .08); }
.elevada {
box-shadow: 0 1px 2px rgb(0 0 0 / .06),
0 .75rem 2rem rgb(102 51 153 / .18); /* doble capa */
}
.interna { box-shadow: inset 0 -3px 0 #472272; } /* subrayado grueso */
.sombra-hover:hover { transform: translateY(-3px);
box-shadow: 0 1rem 2rem rgb(0 0 0 / .15); }
- Receta moderna: x=0, blur generoso, color negro con alpha bajo — sombra «de aire», no la dura gris de hace una década.
- Doble sombra (una corta + una amplia) simula luz ambiental real.
- El combo hover translateY + shadow crece es EL microfeedback estándar.
outline NO es border (y eso salva accesibilidad)
| border | outline |
|---|---|
| ocupa espacio del layout | NO ocupa espacio: se dibuja encima |
| por lado configurable | solo completo |
| decorativo | el foco del teclado lo usa (eco cap. 4) |
filter: drop-shadow para FORMAS
.icono-svg { filter: drop-shadow(0 4px 6px rgb(0 0 0 / .25)); }
drop-shadow sigue el ALFA del elemento: en un PNG transparente o SVG, la sombra abraza la figura, no su caja rectangular.
Puntos clave
- 999px para píldoras; 50% para círculos perfectos.
- Sombra de aire: x=0, blur alto, negro translúcido.
- Hover: translateY(-3px) + sombra mayor = feedback vivo.
- outline no ocupa espacio y pertenece al foco.
13 · Flujo normal, display y tipos de caja
Intermedio ~13 minAntes de flexbox hay que entender qué hace el navegador SIN ayuda: el flujo normal y los dos mundos de display.
Flujo normal: la apilada por defecto
- Bloques: uno debajo del otro, ocupando TODO el ancho disponible.
- Inline: fluyen como palabras en un renglón y pasan a la línea siguiente.
Los tres displays clásicos
| display | Ancho/alto propios | Vive junto a… |
|---|---|---|
block | SÍ respeta ambos | nadie: línea propia |
inline | NO respeta ninguno | texto (width/height ignorados) |
inline-block | SÍ respeta ambos | hermanos en el mismo renglón |
inline-block fue EL hack de las grillas antiguas. Hoy existe para casos puntuales; el layout serio lo hacen flex/grid.
display moderno: dos valores, un concepto
span.tarjeta {
display: block flex;
/* exterior: se comporta como bloque ante hermanos
interior: sus HIJOS participan en formato flex */
}
- Cada display tiene cara EXTERNA (cómo me ven mis hermanos) e INTERNA (cómo organizo yo a mis hijos).
display: flexes azúcar deblock flex;inline-flex, deinline flex.- Este modelo explica de golpe por qué un flex container sigue siendo «un bloque» para SU padre pero organiza distinto a sus hijos.
Ocultar: dos formas muy distintas
| Técnica | Ocupa espacio | Foco/lectores |
|---|---|---|
display:none | no | inaccesible: desaparece TODO |
visibility:hidden | sí (invisible) | también inaccesible |
opacity:0 | sí | AÚN recibe foco y clics: trampa clásica |
El puente hacia flexbox
- Hijos block apilan; necesitas filas → margin negativos y floats eran el infierno antiguo.
- Un solo display:flex en el padre cambia el formato interno: los hijos se acomodan en EJE que tú eliges.
- Ese «eje elegible» es la idea central de los próximos 5 capítulos.
Puntos clave
- Flujo normal: bloques apilan, inline corre como texto.
- display tiene cara externa e interna (block flex).
- opacity:0 NO deshabilita interacción — cuidado.
- flex/grid reemplazan todos los hacks históricos.
14 · Flexbox I: el contenedor
Intermedio ~14 minUna sola declaración en el padre transforma a sus hijos. Primero el mapa mental: los DOS ejes.
El eje principal y el eje cruzado
/* main-axis -> -> -> (horizontal por defecto)
cross-axis
|
v (vertical por defecto) */
.nav {
display: flex; /* los hijos entran en formato flex */
}
- main axis: por donde CORREN los hijos (fila o columna).
- cross axis: la perpendicular, donde se estiran o alinean.
- Toda propiedad flex se entiende preguntando: ¿actúa sobre el eje principal o sobre el cruzado? Esa pregunta resuelve el 80% de las confusiones.
flex-direction: elegir el eje
| Valor | Main axis | Uso típico |
|---|---|---|
row (default) | horizontal → | barras, filas de cards |
column | vertical ↓ | páginas enteras, listas verticales |
row-reverse / column-reverse | invertido ←/↑ | chats nuevos-abajo, RTL |
flex-wrap: una fila o varias
.galeria {
display: flex;
flex-wrap: wrap; /* sin esto: los hijos SE APLASTAN en una linea */
gap: 1rem;
}
wrap en contenedores de contenido.gap también vive aquí
.toolbar {
display: flex;
gap: .5rem 1rem; /* fila columna (o uno solo) */
}
/* separadores con combinadores ya no hacen falta */
.toolbar > * + * { margin-left: 1rem; } /* ANTES */
Navbar mínimo real
<nav class="nav">
<a class="marca" href="#">Aroma&Punto</a>
<a href="#">Cafés</a>
<a href="#">Accesorios</a>
</nav>
.nav {
display: flex;
align-items: center; /* adelanto cap. 15: centrado vertical */
gap: 1.5rem;
}
.marca { margin-inline-end: auto; } /* empuja al resto a la derecha:
trucazo que veras mil veces */
Puntos clave
- Dos ejes: main (corren) y cross (perpendicular).
- direction gira los ejes COMPLETOS — cambia qué es «horizontal».
- wrap casi siempre sí; nowrap aplasta.
- margin auto dentro de flex = separador inteligente.
15 · Flexbox II: alineación total
Intermedio ~14 minTres propiedades cubren TODOS los alineamientos posibles. El truco: identificar en qué eje piensa cada una.
El mapa de las tres propiedades
| Propiedad | Eje que gobierna | Actúa sobre… |
|---|---|---|
justify-content | MAIN (corren) | todos los hijos como grupo |
align-items | CROSS | cada hijo individual |
align-content | CROSS | las LÍNEAS entre sí (solo con wrap) |
justify-content: repartir el sobrante del main axis
/* flex-start center flex-end
|XXXX____| |__XXXX__| |____XXXX|
space-between space-around space-evenly
X___X___X _X__X__X_ _X_X_X_ */
- between: primera y última pegadas a los bordes — barras de navegación.
- evenly: espacios IDÉNTICOS incluidos extremos — paginadores.
- Solo actúa si SOBRA espacio; con hijos llenando todo, no hay nada que repartir.
align-items: el centrado vertical legendario
.hero {
display: flex;
align-items: center; /* vertical perfecto, sin hacks */
justify-content: center; /* horizontal */
min-height: 100dvh;
}
/* dos lineas = lo que antes era un mito urbano de CSS */
align-self: la excepción individual
.fila { display: flex; align-items: center; }
.badge-nuevo { align-self: flex-start; } /* este sube solo */
align-content: cuando hay VARIAS líneas
.galeria {
display: flex;
flex-wrap: wrap;
min-height: 60vh;
align-content: center; /* centra el BLOQUE de lineas entero */
}
Sin wrap hay una sola línea y align-content no hace nada visible. Con varias filas envueltas, distribuye el conjunto.
Ejercicio mental final
<!-- tarjeta de producto: precio abajo-derecha -->
<article class="tarjeta">
<h3>Café Chanchamayo</h3>
<p>Tueste medio, notas de chocolate.</p>
<span class="precio">S/ 28</span>
</article>
.tarjeta {
display: flex;
flex-direction: column; /* main axis AHORA es vertical */
min-height: 12rem;
}
.precio {
margin-top: auto; /* en main axis, auto se lo come TODO:
empuja el precio hasta abajo */
text-align: end;
}
- Gira la cabeza: en columna, justify-content sería VERTICAL y align-items HORIZONTAL. margin:auto trabaja sobre el main axis siempre.
Puntos clave
- justify=main, align-items=cross, align-content=líneas.
- Centrado total: dos declaraciones, cero hacks.
- align-self rompe la regla para un hijo.
- margin-auto empuja a lo largo del eje principal.
16 · Flexbox III: los hijos flex
Intermedio ~14 minflex-grow, flex-shrink y flex-basis: el algoritmo que decide quién crece, quién se encoge y desde qué tamaño parte.
Las tres propiedades del hijo
| Propiedad | Controla | Default |
|---|---|---|
flex-grow | cómo reparte el ESPACIO SOBRANTE | 0 (no crece) |
flex-shrink | cómo REPARTE el déficit cuando falta | 1 (todos encogen) |
flex-basis | tamaño de PARTIDA en el main axis | auto (su width/height) |
grow: proporciones, no píxeles
.barra { display: flex; }
.busqueda { flex-grow: 2; } /* recibe 2 partes del sobrante */
.acciones { flex-grow: 1; } /* recibe 1 parte */
/* con 300px sobrantes: busqueda +200px, acciones +100px */
El shorthand que usarás el 95% del tiempo
| Shorthand | Equivale a… | Léase como… |
|---|---|---|
flex: 1 | 1 1 0% | parte igualado desde CERO: todas las columnas idénticas |
flex: auto | 1 1 auto | crece pero respeta el contenido como base |
flex: none | 0 0 auto | tamaño sagrado: ni crece ni encoge (iconos) |
flex: 0 0 12rem | fijo | sidebar de ancho constante |
/* layout clasico sidebar + contenido */
.layout { display: flex; min-height: 100vh; }
.sidebar { flex: 0 0 16rem; } /* fija */
.contenido { flex: 1; } /* se traga el resto */
order: reordenar sin tocar el HTML
.movil .menu { order: 3; }
.movil .logo { order: 1; }
.movil .buscar { order: 2; }
/* default = 0; empates conservan el orden del DOM.
OJO accesibilidad: el orden visual cambia, el de TAB no
(mismo aviso que los floats historicos) */
El adelanto incómodo: min-width:auto
Un hijo flex NUNCA encoge por debajo de su contenido mínimo — incluso con shrink=1. Un párrafo larguísimo sin espacios rompe la fila entera:
.columna {
flex: 1;
min-width: 0; /* libera al hijo: YA puede encoger de verdad */
}
Es LA trampa nº1 de flexbox — capítulo completo de trucos en el 18.
Puntos clave
- grow reparte sobrante; shrink reparte déficit; basis es la partida.
- flex:1 columnas iguales; flex:none tamaños sagrados.
- Sidebar fija + contenido flexible: 0 0 Xrem junto a flex:1.
- order cambia lo visual, no el tab; min-width:0 libera encogido.
17 · Flexbox IV: patrones reales
Intermedio ~15 minCuatro layouts que cubren la mayoría de páginas reales, completos y listos para copiar.
1. Sticky footer (el pie que siempre abajo)
body {
display: flex;
flex-direction: column;
min-height: 100dvh;
}
main { flex: 1; } /* se estira y empuja al footer abajo */
2. Fila de cards a la misma altura
<div class="fila">
<article class="card">...</article>
<article class="card">...</article>
<article class="card">...</article>
</div>
.fila {
display: flex;
gap: 1rem;
}
.card {
flex: 1; /* mismas anchuras */
display: flex;
flex-direction: column; /* tarjeta = columna interna */
}
.card p { flex: 1; } /* el texto absorbe la altura:
botones SIEMPRE al fondo igualados */
3. Media object: imagen + texto (patrón universal)
.media {
display: flex;
gap: .75rem;
}
.media img { flex: none; width: 3rem; height: 3rem;
border-radius: 50%; }
.media .cuerpo { min-width: 0; } /* el texto PUEDE encoger
(trampa del cap.16) */
4. Holy grail: header + nav/lateral/contenido + footer
<body>
<header>Marca</header>
<div class="cuerpo">
<aside>Lateral</aside>
<main>Contenido</main>
<aside class="publi">Promos</aside>
</div>
<footer>Pie</footer>
</body>
body { display: flex; flex-direction: column;
min-height: 100dvh; }
.cuerpo {
flex: 1; /* ocupa el alto medio */
display: flex;
}
.cuerpo aside:first-child { flex: 0 0 14rem; }
.cuerpo main { flex: 1; }
.cuerpo .publi { flex: 0 0 10rem; }
/* en movil, el mismo HTML apila: */
@media (max-width: 48rem) { /* Parte V lo formaliza */
.cuerpo { flex-direction: column; }
}
Checklist mental ante cualquier layout
- ¿Quién es el contenedor? Marca display:flex en el PADRE, no en los hijos.
- ¿Qué eje necesitas? row/column decide TODO lo demás.
- ¿Quién crece? Un solo hijo con flex:1 suele bastar.
- ¿Algo no encoge? min-width:0 en la víctima.
Puntos clave
- Sticky footer = column + main{flex:1}.
- Cards iguales: fila flex + tarjeta columna + texto flex:1.
- Media object: icono flex:none + cuerpo min-width:0.
- Holy grail son 6 líneas reales de CSS.
18 · Flexbox V: trampas y trucos finos
Intermedio ~14 minLos cinco comportamientos que rompen layouts reales — y sus soluciones de una línea.
1. min-width:auto: el hijo que no se deja encoger
/* sintoma: una fila flex se desborda aunque todo tenga flex */
.fila { display: flex; }
.texto { flex: 1; } /* con una URL larguisima adentro... */
/* diagnostico: los hijos tienen un min-width INVISIBLE
igual a su contenido mas ancho. Solucion: */
.texto { min-width: 0; } /* libera al hijo para encoger */
- Víctimas frecuentes: URLs sin espacios, tablas anchas, pre de código.
- Para imágenes dentro del hijo:
max-width:100%además (cap. 7). Para tablas: envolverlas en overflow-x:auto (cap. 11).
2. align-items:stretch estira imágenes
.media { display: flex; align-items: center; gap: .75rem; }
.media img {
width: 3rem;
height: 3rem; /* SIN height, stretch la aplastaria:
el default de align-items es stretch */
El default stretch solo actúa cuando el hijo NO tiene altura definida: por eso avatares «estirados» en filas flex.
3. position:sticky que no pega dentro de flex
.layout { display: flex; }
.lateral { position: sticky; top: 1rem;
align-self: flex-start; /* ¡LA CLAVE!
/* stretch estira el lateral a toda la columna:
sticky no puede moverse si ya ocupa TODO */
4. margins auto: separadores quirúrgicos
.nav { display: flex; gap: 1rem; }
.nav .cta { margin-inline-start: auto; } /* empuja SOLO al CTA */
/* dos autos reparten por partes iguales */
.a { margin-inline-end: auto; }
.b { margin-inline-start: auto; }
5. Anidado: cada nivel su propio eje
<div class="fila"> <!-- flex row -->
<article class="card">
<div class="card-cabecera"></div>
<p></p>
<button></button>
</article>
</div>
.fila { display: flex; gap: 1rem; }
.card { display: flex; flex-direction: column; }
.card .cabecera { display: flex; justify-content: space-between; }
- Regla anti-confusión: NUNCA pienses en más de un contenedor a la vez. Cada nivel declara SU display y SUS ejes, sin heredar confusión.
Kit de depuración flex
- F12 → selecciona el contenedor → badge «flex» clicable: DevTools dibuja main/cross axis sobre la página real.
- Hermanos con outline temporal distinto por color: quién mide qué.
- Pregunta canónica: ¿el problema está en el CONTENEDOR o en un HIJO? (justify/align vs grow/shrink/self).
Puntos clave
- min-width:0 libera; max-width:100% protege imágenes.
- stretch es el default: define alturas o usa align-self.
- Sticky en flex exige align-self:flex-start.
- Margins auto = separadores; anidar sin miedo nivel por nivel.
19 · Grid I: el contenedor
Intermedio ~14 minSi flexbox organiza una DIMENSIÓN, grid dibuja la hoja cuadriculada completa ANTES de colocar nada.
El salto conceptual
| Flexbox | Grid | |
|---|---|---|
| Diseñas… | el CONTENIDO fluye y empuja | la REJILLA primero, huecos incluidos |
| Ejes | uno principal (+ cruzado) | FILAS y COLUMNAS simultáneas |
| Ideal para… | componentes, barras, filas | páginas, dashboards, galerías |
Tu primera rejilla
.pagina {
display: grid;
grid-template-columns: 15rem 1fr 12rem;
grid-template-rows: auto 1fr auto;
gap: 1rem;
}
/* 15rem | flexible | 12rem — tres columnas exactas
filas: cabecera auto, cuerpo 1fr, pie auto */
fr: la unidad de reparto justo
.mitades { grid-template-columns: 1fr 1fr; }
/* NO significa "50%": significa "partes iguales DEL SOBRANTE"
tras pagar gaps y pistas fijas */
.asimetrico { grid-template-columns: 2fr 1fr; }
/* 2 partes vs 1 parte del espacio disponible */
- Mezcla perfectamente válida:
10rem 1fr 1fr— pista fija + dos flexibles. - Es la misma lógica de flex-grow pero declarada en el CONTENEDOR.
repeat(): escribir menos
.doce { grid-template-columns: repeat(12, 1fr); }
.galeria {
display: grid;
grid-template-columns: repeat(3, 1fr); /* tres iguales */
gap: 1rem;
}
/* nombres legibles para lineas (cap. 20 las usara) */
.layout {
grid-template-columns:
[inicio] 15rem
[contenido] 1fr
[final];
}
Colocación automática: sin decir dónde
<div class="galeria">
<figure>1</figure><figure>2</figure><figure>3</figure>
<figure>4</figure><figure>5</figure><figure>6</figure>
</div>
Sin propiedades extra, cada figure cae en la siguiente celda libre, fila por fila. El flujo automático de grid.
Filas implícitas vs explícitas
.galeria {
grid-template-rows: none;
grid-auto-rows: minmax(10rem, auto); /* altura minima para las
filas que APARECEN solas */
}
- Las filas que declares son EXPLÍCITAS; las que surgen por contenido, IMPLÍCITAS, y se controlan con grid-auto-rows/columns.
Puntos clave
- Grid = rejilla previa de filas Y columnas.
- fr reparte el SOBRANTE como flex-grow.
- repeat(n, 1fr) para pistas idénticas.
- auto-rows gobierna las filas espontáneas.
20 · Grid II: colocación y áreas
Intermedio ~14 minDos formas de decir «tú vas AQUÍ»: por líneas numeradas o dibujando el plano con áreas.
Líneas numeradas: la dirección técnica
.destacado {
grid-column: 1 / 3; /* desde linea 1 HASTA linea 3 (2 pistas) */
grid-row: 1;
}
/* span: "ocupa N pistas" sin contar lineas */
.amplio { grid-column: span 2; }
/* desde la 2 hasta el final */
.lateral { grid-column: 2 / -1; }
- Las líneas se cuentan en los BORDES, no en las celdas: 3 columnas = 4 líneas verticales. −1 es la última, como en Python (eco js_01 índices negativos).
grid-template-areas: el plano dibujado
.pagina {
display: grid;
grid-template-columns: 15rem 1fr;
grid-template-areas:
"cabecera cabecera"
"lateral principal"
"pie pie";
}
.cabecera { grid-area: cabecera; }
.lateral { grid-area: lateral; }
.principal { grid-area: principal; }
.pie { grid-area: pie; }
El CSS SE VEE como la página final. Cada palabra es una zona; repetida en
varias celdas, la zona las abarca todas. Un punto . deja hueco:
grid-template-areas:
"cabecera cabecera ."
"principal principal publi";
Replantear en responsive (adelanto Parte V)
@media (max-width: 48rem) {
.pagina {
grid-template-columns: 1fr;
grid-template-areas:
"cabecera"
"principal"
"lateral"
"pie";
}
}
/* MISMO HTML, otro orden visual: lateral pasa debajo del contenido */
Superposición intencional
<div class="tarjeta">
<img src="cafe.webp" alt="">
<p class="cinta">¡Nuevo!</p>
</div>
.tarjeta {
display: grid; /* un solo area para todos */
}
.tarjeta img,
.cinta { grid-area: 1 / 1; } /* ambos EN LA MISMA CELDA */
.cinta { align-self: start; justify-self: end;
background: #b23117; color: #fff;
padding: .25rem .6rem; }
- Celdas compartidas = capas sin position:absolute. La cinta vive sobre la foto y hereda el centrado del grid.
- justify-self/align-self mueven cada hijo DENTRO de su celda.
Puntos clave
- Líneas: bordes contados; span N evita aritmética; -1 es el final.
- template-areas = plano legible; puntos = huecos.
- Mismo HTML, nuevo plano en cada breakpoint.
- grid-area 1/1 superpone capas sin absolute.
21 · Grid III: patrones con minmax y auto-flow
Intermedio ~14 minLa línea de CSS más famosa del responsive moderno: una galería que se adapta SIN una sola media query.
minmax(): pista con límites
.layout {
grid-template-columns: minmax(12rem, 18rem) 1fr;
}
/* lateral: entre 12 y 18 rem segun espacio
principal: todo el resto */
EL patrón: repeat + auto-fill + minmax
.galeria {
display: grid;
gap: 1rem;
grid-template-columns:
repeat(auto-fill, minmax(14rem, 1fr));
}
- auto-fill pregunta: «¿cuántas columnas de mínimo 14rem me caben?» — 4 en desktop, 2 en tablet, 1 en móvil. Solo.
- Cada tarjeta mantiene legibilidad mínima garantizada por el min.
- Este patrón reemplaza decenas de media queries históricas.
auto-fill vs auto-fit: la diferencia fina
| Con pocos items (2 en pantalla ancha) | |
|---|---|
auto-fill | mantiene los huecos vacíos: cards del ancho normal, alineadas a la izquierda |
auto-fit | COLAPSA los huecos: las 2 cards se estiran a lo ancho total |
Regla práctica: catálogos con contenido variable → auto-fill; listas cortas controladas → auto-fit si quieres estirón.
grid-auto-flow: dense rellena huecos
.mosaico {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
grid-auto-flow: dense; /* retrocede a llenar agujeros */
}
.destacada { grid-column: span 2; grid-row: span 2; }
- Sin dense, un item span 2 deja celdas huérfanas. Con dense, el flujo RETROCEDE y tapa los agujeros con items posteriores.
- Ojo orden visual: puede diferir del DOM (mismo aviso de order).
Galería editorial completa
<div class="mosaico">
<figure class="destacada"><img src="hero.webp" alt=""></figure>
<figure><img src="1.webp" alt=""></figure>
<figure><img src="2.webp" alt=""></figure>
<!-- ... -->
</div>
.mosaico img {
width: 100%;
height: 100%;
object-fit: cover; /* la foto llena su celda sin deformarse */
}
.mosaico figure {
aspect-ratio: 1; /* celdas cuadradas siempre */
overflow: hidden;
}
Puntos clave
- repeat(auto-fill,minmax(X,1fr)): responsive sin queries.
- fill conserva huecos; fit colapsa y estira.
- dense tapa agujeros de spans grandes.
- object-fit cover + aspect-ratio: fotos perfectas en cualquier celda.
22 · Grid vs Flexbox: la decisión
Intermedio ~12 minNo son rivales: son herramientas de escalas distintas. La pregunta correcta no es cuál es mejor, sino quién decide el acomodo.
La pregunta clave: ¿quién manda?
| Pregunta | Herramienta |
|---|---|
| El CONTENIDO dicta su tamaño y fluye (texto, chips, botones) | Flexbox |
| la REJILLA dicta huecos y el contenido encaja (páginas, dashboards) | Grid |
| Una dimensión (fila O columna) | Flexbox |
| Dos dimensiones a la vez (fila Y columna) | Grid |
En una página real conviven ambos
<body>
<header class="topbar">...</header> <!-- FLEX -->
<div class="pagina"> <!-- GRID -->
<aside></aside>
<main>
<div class="cards">...</div> <!-- GRID auto-fill -->
<article class="media">...</article> <!-- FLEX -->
</main>
</div>
<footer>...</footer>
</body>
body { display: flex; flex-direction: column; min-height: 100dvh; }
.pagina { display: grid; grid-template-columns: minmax(12rem,16rem) 1fr; }
.cards { display: grid;
grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); }
.media { display: flex; gap: .75rem; }
Matriz de decisión rápida
| Necesidad | Elegir |
|---|---|
| Navbar, toolbar, fila de botones | flex + margin-auto |
| Cards alineadas en filas responsivas | grid auto-fill/minmax |
| Alinear un icono junto a texto | flex align-items:center |
| Layout de página con laterales | grid areas / minmax+1fr |
| Sobreponer elementos sin absolute | grid celda compartida 1/1 |
| Empujar un elemento al fondo de una tarjeta | flex column + margin-top:auto |
Errores típicos de mezcla
- Usar grid para UNA fila de iconitos: over-engineering — flex basta.
- Luchar contra flex para un layout de página completo con laterales: grid lo resuelve declarativo.
- Olvidar que dentro de una celda/grid-area puedes poner display:flex sin conflicto: cada nivel usa SU herramienta.
Puntos clave
- Contenido empuja → flex; rejilla manda → grid.
- Una dimensión vs dos.
- La página real alterna ambos por niveles.
- Dentro de cada herramienta, sus trucos siguen válidos.
23 · Media queries
Intermedio ~12 minLa sintaxis oficial para preguntarle al entorno: ancho, orientación, preferencias del usuario y hasta la impresora.
Anatomía
@media (min-width: 48rem) {
.nav { gap: 2rem; }
}
@media (min-width: 48rem) and (max-width: 80rem) { /* rango */ }
@media (width >= 48rem) and (width <= 80rem) { /* sintaxis
de rango moderna: mas legible y sin max-width mental */ }
Más allá del ancho
| Query | Para qué |
|---|---|
(orientation: portrait) | ajustes de tablet vertical |
(hover: none) | desactivar efectos solo-mouse en táctil |
(pointer: coarse) | targets más grandes en dedos (eco HTML a11y) |
print | hoja de impresión: ocultar navs, links con href visible |
@media print {
nav, .botones { display: none; }
a[href^="http"]::after { content: " (" attr(href) ")"; }
}
Preferencias del usuario: las queries de respeto
@media (prefers-color-scheme: dark) {
:root { --fondo: #1a1420; --texto: #eee; } /* adelanto cap. 31 */
}
@media (prefers-reduced-motion: reduce) {
* { animation-duration: .01ms !important;
transition-duration: .01ms !important; }
}
- reduced-motion respeta a usuarios con vértigo/nausea por movimiento — obligatorio moral y práctico antes de animar (Parte VI).
- Ojo: el selector universal + important es un martillo aceptable AQUÍ.
Errores clásicos
- device-width: obsoleto; usa width normal. Los DPR antiguos lo volvieron mentiroso.
- Píxeles físicos vs CSS: las queries leen px CSS (ya pasan por el device pixel ratio), no midas tu pantalla física.
- Solo max-width: cascada de parches; mobile-first con min-width es más simple (cap. 24).
Puntos clave
- Rangos modernos: (width >= X) legible.
- hover/pointer adaptan interacción táctil.
- prefers-* = preferencias REALES del usuario.
- print: la query olvidada que impresiona en entrevistas.
24 · Mobile-first y breakpoints
Intermedio ~13 minDiseñar desde el teléfono hacia arriba produce CSS más simple que recortar un desktop hacia abajo.
Las dos escuelas
| Desktop-first (max-width) | Mobile-first (min-width) |
|---|---|
| CSS completo para desktop… | CSS base mínimo para móvil… |
| y vas QUITANDO/rompiendo abajo | y vas AÑADIENDO complejidad arriba |
| móvil hereda parches y sobrepesos | desktop hereda mejoras progresivas |
Mobile-first gana porque el CSS base (el que ve todo dispositivo antes de cualquier query) queda limpio y liviano.
Breakpoints por CONTENIDO, no por dispositivos
- No «iPhone=390px»: los modelos cambian cada año.
- Abre tu página y estrecha la ventana: cuando el contenido ROMPE (texto apretado, cards aplastadas), ahí hay un breakpoint.
:root {
--bp-md: 48rem; /* ~768px: tablets / nav horizontal */
--bp-lg: 64rem; /* ~1024px: laterales aparecen */
--bp-xl: 80rem; /* ~1280px: contenedor maximo */
}
@media (min-width: var(--bp-lg)) { }
Refactor mobile-first del holy grail
/* BASE = movil: columna unica, cero queries */
body { display: flex; flex-direction: column; min-height: 100dvh; }
.cuerpo {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
/* tablet: lateral aparece */
@media (min-width: 48rem) {
.cuerpo { grid-template-columns: minmax(10rem, 14rem) 1fr; }
}
/* desktop: tercera columna */
@media (min-width: 64rem) {
.cuerpo { grid-template-columns: minmax(12rem,16rem) 1fr minmax(8rem,10rem); }
}
Higiene de queries
- Pocas y grandes: 2–3 breakpoints bien elegidos vencen a 8 micro-parches.
- Agrupa por componente: cada bloque CSS con sus queries justo debajo, no un monolito @media final imposible de mantener.
- Nunca ocultes contenido crítico en móvil por comodidad: reordena (grid areas, cap. 20) o colapsa con details.
Puntos clave
- Base móvil limpia + min-width ascendentes.
- Breakpoints donde rompe TU contenido.
- Queries junto al componente que modifican.
- Reordenar con areas > esconder contenido.
25 · clamp() y unidades fluidas
Intermedio ~12 minTipografía y espaciados que escalan SOLOS entre un mínimo y un máximo — sin media queries.
La fórmula
clamp(MINIMO, PREFERIDO, MAXIMO);
/* el navegador usa PREFERIDO;
lo recorta si se sale de los limites */
h1 { font-size: clamp(1.8rem, 5vw, 3rem); }
- En móvil pequeño 5vw da poco → recortado al mínimo 1.8rem.
- En monitor grande 5vw daría demasiado → tope en 3rem.
- Entre ambos: fluido continuo, sin saltos de breakpoint.
El preferido perfecto: mezclar unidades
:root {
--fs-hero: clamp(2.2rem, 6vw + .5rem, 4.5rem);
--fs-h2: clamp(1.4rem, 2vw + .8rem, 2rem);
--fs-cuerpo: clamp(1rem, .9rem + .3vw, 1.15rem);
/* espaciados fluidos de seccion */
--seccion-y: clamp(2.5rem, 8vh, 6rem);
}
.hero h1 { font-size: var(--fs-hero); line-height: 1.1; }
Xvw + Yrem: parte proporcional a pantalla + base estable. El rem evita que en móviles muy chicos el texto colapse.- Accesibilidad incluida: como el mínimo está en rem, subir la fuente del navegador respeta el clamp (eco cap. 6).
Dónde brilla y dónde NO usarlo
| Sí | No |
|---|---|
| tamaños tipográficos | colores, bordes de 1px |
| paddings verticales de secciones | anchos de layout estructural (eso es grid) |
| gaps grandes entre bloques | valores que deben CAMBIAR de forma distinta (ahí: query) |
Escala completa de la casa
:root {
--space-1: clamp(.25rem, .2rem + .25vw, .5rem);
--space-2: clamp(.5rem, .4rem + .5vw, 1rem);
--space-3: clamp(1rem, .8rem + 1vw, 2rem);
--space-4: clamp(2rem, 1.5rem + 2vw, 4rem);
}
/* todo el sistema respira con la pantalla:
una sola fuente de verdad para margenes/paddings/gaps */
font-size: 5vw sin clamps
es texto ilegible en móviles y gigantesco en 4K. SIEMPRE clamp(min, fluido, max).Puntos clave
- clamp(min, preferido, max): fluido con cinturón.
- Preferido = vw + rem: escala pero conserva base.
- Escala de espacios en :root = sistema completo.
- Para cambios ESTRUCTURALES sigue existiendo la media query.
26 · Container queries: responsive del componente
Intermedio ~13 minEl cambio de paradigma: el componente responde a SU TAMAÑO, no al de la ventana. Una card se ve igual de bien en un sidebar que a pantalla completa.
El problema que las media queries no resuelven
<!-- la MISMA card en dos contextos -->
<aside class="sidebar">
<article class="card">...</article> <!-- angosta: 280px -->
</aside>
<main>
<article class="card">...</article> <!-- ancha: 700px -->
</main>
Con media queries, ambas reaccionan al VIEWPORT: si la ventana es grande pero la sidebar angosta, la card de adentro recibe estilos de «desktop» y revienta su ranura.
La solución en dos pasos
/* 1) declarar el contenedor medible */
.sidebar,
main {
container-type: inline-size; /* mide su INLINE (ancho) */
}
/* 2) consultar desde los componentes */
.card {
display: flex;
flex-direction: column; /* base movil del componente */
}
@container (min-width: 26rem) {
.card {
flex-direction: row; /* hay espacio: imagen al lado */
}
.card img { width: 10rem; height: auto; }
}
Unidades del contenedor: cqi
.card h3 {
font-size: clamp(1rem, 5cqi, 1.5rem);
}
/* cqi = 1% del ancho del CONTENEDOR (no del viewport)
la tipografia escala con la ranura real */
Media query vs container query
| @media (min-width…) | @container (min-width…) |
|---|---|
| ¿Cuánto mide la VENTANA? | ¿cuánto mide MI CONTENEDOR? |
| diseño MACRO: columnas de página, navs | diseño MICRO: cards, widgets, formularios |
| una sola verdad por página | cada instancia según su ranura |
Buenas prácticas
- Nombra contenedores cuando hay varios niveles:
container: tarjeta / inline-size;y consulta@container tarjeta (min-width: …). - container-type crea containment: evita ponerlo en elementos cuya altura depende del contenido flotante — úsalo en wrappers dedicados.
- Estrategia moderna: media queries para la página, container queries para todo componente reutilizable.
Puntos clave
- container-type inline-size habilita la consulta.
- @container responde a la ranura real del componente.
- cqi escala tipografía con el contenedor.
- Macro=media, micro=container.
27 · Layout responsivo integrador
Intermedio ~15 minTodo el arsenal de las Partes III–V en una página: grid areas que mutan, flex interno, clamp y container queries.
La arquitectura en un vistazo
/* ===== sistema global (caps. 25/31) ===== */
:root {
--marca: #663399;
--fs-hero: clamp(2rem, 5vw + .5rem, 3.5rem);
--seccion: clamp(2rem, 6vh, 4rem);
}
/* ===== pagina: mobile-first + areas (caps. 20/24) ===== */
body { display: flex; flex-direction: column;
min-height: 100dvh; }
.pagina {
display: grid;
gap: var(--seccion);
grid-template-areas:
"hero"
"catalogo"
"lateral";
}
@media (min-width: 64rem) {
.pagina {
grid-template-columns: minmax(12rem, 16rem) 1fr;
grid-template-areas:
"lateral hero"
"lateral catalogo";
}
}
Catálogo responsivo sin queries (cap. 21)
.catalogo {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
}
Card como componente consciente (cap. 26)
.catalogo { container-type: inline-size; }
.card {
display: flex;
flex-direction: column;
}
.card .cuerpo { display: flex; flex-direction: column;
flex: 1; }
.card .precio { margin-top: auto; }
@container (min-width: 24rem) {
.card { flex-direction: row; }
.card img { width: 9rem; height: auto; }
}
Detalles de pulido
.hero {
background:
radial-gradient(60rem 22rem at 85% -10%,
rgba(102,51,153,.16), transparent 60%);
padding-block: var(--seccion);
}
.hero h1 { font-size: var(--fs-hero); max-width: 30ch; }
img { max-width: 100%; height: auto; } /* cap. 7 */
Checklist responsive final
- ¿El HTML base es móvil puro sin parches?
- ¿Los breakpoints nacieron de romper TU contenido?
- ¿Las cards sobreviven a cualquier ranura gracias a @container?
- ¿Tipografía y espacios respiran con clamp sin saltos?
- ¿Probaste con zoom de navegador al 200%? (eco accesibilidad)
Puntos clave
- Sistema de variables + clamp = identidad fluida.
- Areas que mutan > esconder contenido.
- Catálogo auto-fill; card con @container.
- Zoom 200% como prueba final.
28 · Transiciones
Intermedio ~12 minEl cambio de estado A al estado B, suavizado. La diferencia entre una UI que «salta» y una que respira.
La sintaxis completa
.boton {
background: #663399;
transition: background .25s ease-out,
translate .25s ease-out;
}
.boton:hover {
background: #472272;
}
/* shorthand: propiedad duracion timing delay */
- La transición se declara en el estado BASE, así anima ida Y vuelta.
- Varias propiedades en lista separada por comas; cada una con su timing.
Timing functions: la personalidad del movimiento
| Tipo | Sensación |
|---|---|
linear | robótico: solo para loops infinitos |
ease (default) | vale, pero genérico |
ease-out | frena al llegar: entradas de UI |
ease-in | arranca suave y acelera: salidas |
cubic-bezier(.34,1.56,.64,1) | rebote sutil: el toque premium |
Duraciones honestas
- Microinteracciones (hover, focus): 150–300ms. Más lento se siente pesado.
- Paneles/modales grandes: 300–450ms.
- Nada interactivo debería tardar más de .5s en responder visualmente.
El patrón dropdown accesible
.menu-panel {
opacity: 0;
visibility: hidden;
translate: 0 -.5rem;
transition: opacity .2s ease, translate .2s ease,
visibility 0s linear .2s; /* ocultar DESPUES */
}
.menu:hover .menu-panel,
.menu:focus-within .menu-panel {
opacity: 1;
visibility: visible;
translate: 0 0;
transition-delay: 0s; /* aparecer YA */
}
Acordeón moderno sin JS ni height mágica
.acordeon-cuerpo {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows .3s ease;
}
.abierto .acordeon-cuerpo { grid-template-rows: 1fr; }
.acordeon-cuerpo > div { overflow: hidden; }
Puntos clave
- Transición en estado base: anima ida y vuelta.
- ease-out entra, ease-in sale; 150–300ms.
- opacity+visibility sustituye a display:none animable.
- Acordeón: 0fr/1fr con transition.
29 · Animaciones keyframes
Intermedio ~13 minDonde las transiciones llegan (dos estados), las keyframes continúan: coreografías completas sin JavaScript.
Anatomía de @keyframes
@keyframes entrar {
from {
opacity: 0;
translate: 0 1rem;
}
to {
opacity: 1;
translate: 0 0;
}
}
.tarjeta {
animation: entrar .4s ease-out both;
}
/* both = aplica el estado inicial ANTES y conserva el final */
El shorthand y sus piezas
| Pieza | Ejemplo | Nota |
|---|---|---|
| nombre | entrar | el @keyframes definido |
| duración | .4s | primera marca de tiempo |
| timing | ease-out | igual que transiciones |
| iteración | infinite / 3 | para loops |
| direction | alternate | ida y vuelta: pulso natural |
| fill-mode | both | mantiene estados extremos |
Loader clásico en 10 líneas
@keyframes girar {
to { rotate: 360deg; }
}
.spinner {
width: 2rem; height: 2rem;
border: 3px solid rgb(102 51 153 / .25);
border-top-color: #663399;
border-radius: 50%;
animation: girar .8s linear infinite;
}
Stagger: cascada de entradas
@keyframes subir {
from { opacity: 0; translate: 0 1.5rem; }
}
.lista li {
animation: subir .35s ease-out both;
}
.lista li:nth-child(2) { animation-delay: .07s; }
.lista li:nth-child(3) { animation-delay: .14s; }
.lista li:nth-child(4) { animation-delay: .21s; }
Un mismo keyframe + delays escalonados = efecto premium. Para listas dinámicas largas, JS setea el delay inline (eco js_01).
Control por interacción
@keyframes pulso {
50% { scale: 1.08; }
}
.cta { animation: pulso 1.6s ease-in-out infinite; }
.cta:hover { animation-play-state: paused; } /* pausa al hover */
.cta:focus-visible { outline-offset: 4px; }
- Percentajes permiten hitos intermedios: 0% → 50% → 100% con varias propiedades a la vez.
- Nunca animes width/height/top en keyframes: mismos costos del cap. 28.
Puntos clave
- @keyframes define; animation aplica; both evita saltos.
- alternate + infinite = pulsos naturales.
- Stagger = delays escalonados sobre un solo keyframe.
- play-state pausa sin borrar nada.
30 · Transformaciones
Intermedio ~12 minMover, girar y escalar SIN tocar el layout: las transforms son el motor visual de hover states y animaciones.
Las cuatro básicas
.mover { translate: 8px -4px; } /* desplazamiento */
.escala { scale: 1.15; } /* crece 15% */
.gira { rotate: 45deg; }
.inclina { transform: skewX(-6deg); } /* la unica que exige
la sintaxis vieja */
- Las propiedades individuales (translate/scale/rotate) son CSS moderno: se combinan sin pelearte con matrices de transform.
- NUNCA ocupan espacio del layout: el vecino no se mueve — a diferencia de margin/position, que reflowean todo.
El combo estrella: hover físico
.tarjeta {
transition: translate .25s ease, box-shadow .25s ease;
}
.tarjeta:hover {
translate: 0 -.35rem;
box-shadow: 0 1rem 2rem rgb(0 0 0 / .14);
}
transform-origin: el pivote
.menu-abre {
transform-origin: top center; /* crece desde arriba */
scale: 0; /* cerrado */
}
.menu-padre:hover .menu-abre,
.menu-padre:focus-within .menu-abre { /* teclado incluido */
scale: 1;
}
Flip card completo (el ejercicio clásico)
<div class="flip" tabindex="0">
<div class="flip-interno">
<div class="cara frente">Pregunta</div>
<div class="cara dorso">Respuesta</div>
</div>
</div>
.flip { perspective: 50rem; } /* profundidad 3D */
.flip-interno {
position: relative;
transition: rotate .5s ease;
transform-style: preserve-3d;
}
.flip:hover .flip-interno,
.flip:focus-visible .flip-interno { rotate: y 180deg; }
.cara { backface-visibility: hidden; } /* oculta el reverso */
.dorso { rotate: y 180deg; /* preparado al dorso */
position: absolute; inset: 0; }
Por qué transform + opacity son «gratis»
El compositor del navegador los procesa en GPU sin recalcular layout ni pintada completa (eco js_01 cap. 39). Animar width o top obliga a RE-layout de la página en cada frame — la diferencia entre 60fps y tartamudeo. Regla: solo transform y opacity se animan; lo demás, excepción justificada.
Puntos clave
- translate/scale/rotate modernos; no ocupan layout.
- origin cambia el pivote; focus-within para teclado.
- Flip = perspective + preserve-3d + backface hidden.
- Solo transform/opacity van a 60fps seguros.
31 · Custom properties: variables y theming
Intermedio ~13 minLa misma plantilla que estás leyendo cambia de tema con variables. Aquí está su mecánica completa.
Sintaxis y herencia real
:root {
--marca: #663399;
--espacio: 1rem;
}
.boton {
background: var(--marca);
padding: var(--espacio);
color: var(--marca-alterna, #b23117); /* fallback si no existe */
}
- Se declaran con
--(cualquier valor) y se leen con var(). - Heredan como cualquier propiedad: definidas en :root, vivas en todo el árbol; redefinibles por rama.
Theming por rama: el modo oscuro honesto
:root {
--fondo: #faf7f2;
--texto: #2a2430;
--superficie: #fff;
}
[data-theme="oscuro"] {
--fondo: #1a1420;
--texto: #ece6f4;
--superficie: #251d30;
}
body { background: var(--fondo); color: var(--texto); }
.card { background: var(--superficie); }
// el toggle del navbar (eco js_01):
document.documentElement.dataset.theme = "oscuro";
// TODA la pagina cambia de una vez: cero clases repetidas
Variables que se calculan solas
:root {
--marca-h: 270; /* tono HSL de la marca */
--marca: hsl(var(--marca-h) 50% 40%);
--marca-suave: hsl(var(--marca-h) 50% 92%);
--marca-hover: hsl(var(--marca-h) 55% 32%);
}
/* cambiar UN numero re-tematiza toda la paleta */
Contexto local: componentes autocontenidos
.card {
--pad: 1rem;
padding: var(--pad);
}
.card--compacta {
--pad: .5rem; /* SOLO esta variante cambia el padding interno */
}
JS lee y escribe (puente js_01)
const estilos = getComputedStyle(document.documentElement);
const marca = estilos.getPropertyValue("--marca").trim();
// "para medir" un color en canvas o exportarlo
document.documentElement.style.setProperty("--marca", "#b23117");
// theming dinamico desde la API: color de marca por tienda
@media (min-width: var(--bp)) es inválido).
Para eso, custom properties de breakpoint escritas a mano o preprocesador.Puntos clave
- --declara, var() lee; heredan y cascada normal.
- Tema = redefinir variables bajo [data-theme].
- Composición HSL para paletas de una sola cifra.
- JS puede leerlas/escribirlas en vivo.
32 · Cascada moderna: @layer, :is() y BEM
Intermedio ~13 minCuando el proyecto crece, la guerra de especificidad se gana con ARQUITECTURA, no con fuerza.
@layer: capas con jerarquía declarada
@layer reset, base, componentes, utilidades;
@layer base {
p { line-height: 1.6; }
}
@layer componentes {
.card p { line-height: 1.5; } /* gana por ORDEN de capa,
no por su especificidad */
}
@layer utilidades {
.texto-rojo { color: #b23117; } /* siempre disponible al final */
}
- La línea de declaración FIJA la prioridad: utilidades vencen a base aunque sus selectores pesen menos. La tripleta del cap. 5 queda subordinada al orden de capas.
- Estilos sin capa ganan sobre TODAS las capas — ideal para overrides puntuales conscientes.
- Perfecto para aislar CSS de terceros: tu capa encima de la librería importada.
:is() y :where(): agrupar con cabeza
/* antes: repetir o engordar especificidad */
header a, main a, footer a { color: var(--marca); }
/* :is(): toma la especificidad del MAS ESPECIFICO dentro */
:is(header, main, footer) a { color: var(--marca); }
/* :where(): especificidad CERO total */
:where(h1, h2, h3) { margin-block: .5em; }
/* cualquier regla posterior puede vencerla sin pelear */
BEM en 5 minutos
<article class="card card--oferta">
<div class="card__precio">S/ 28</div>
<button class="btn btn--grande">Comprar</button>
</article>
| Pieza | Sintaxis | Ejemplo |
|---|---|---|
| Bloque | palabra | .card, .btn |
| Elemento (parte del bloque) | __ | .card__precio |
| Modificador (variante) | -- | .card--oferta, .btn--grande |
- Toda clase plana y autocontenida: especificidad uniforme (0,1,0), cero cascadas sorpresivas, HTML autodocumentado.
- Nombres largos son baratos; bugs de especificidad son caros.
Puntos clave
- @layer declara jerarquía: orden > tripleta.
- :is() hereda el peso mayor; :where() pesa CERO.
- BEM: bloques __elementos --modificadores, todo plano.
- Arquitectura primero; !important nunca.
33 · Rendimiento, accesibilidad visual y debugging
Intermedio ~14 minEl cierre técnico: qué le cuesta caro al navegador, qué le cuesta caro al usuario y cómo encontrarlo todo en DevTools.
Los tres costos de cada frame
- Layout: recalcular geometría (width, top, flex/grid changes) — el más caro.
- Paint: repintar píxeles (color, background, box-shadow).
- Composite: mover capas ya pintadas — transform y opacity SOLO.
/* animar esto: NO animar esto: */
/* transform width / height */
/* opacity top / left */
/* margin / padding */
Selectores: la verdad moderna
- El mito «los selectores largos son lentos» murió con los motores actuales: el costo real está en CANTIDAD de estilos que cambian, no en el matching.
- Sigue siendo cierto que selectores planos (BEM) son más mantenibles — por legibilidad, no por microsegundos.
- CSS bloquea el render: hojas mínimas críticas inline + resto diferido (eco HTML cap. 26).
Accesibilidad visual obligatoria
:focus-visible {
outline: 3px solid var(--marca);
outline-offset: 2px; /* NUNCA outline:none a secas */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: .01ms !important;
transition-duration: .01ms !important;
}
}
@media (prefers-contrast: more) { /* usuarios de bajo contraste */
.texto-suave { color: #000; }
}
- Contraste AA 4.5:1 mínimo texto/fondo (eco HTML cap. 25) — DevTools color picker muestra el ratio en vivo.
- Tamaños táctiles ≈44px; estados hover SIEMPRE duplicados en focus.
DevTools: las herramientas del oficio
| Herramienta | Resuelve… |
|---|---|
| Styles panel tachado | quién venció a quién (cascada cap. 5) |
| Badge grid/flex clicable | dibuja ejes y líneas sobre tu página real |
| Rendering → Paint flashing | pinta verde lo que se REPINTA: si parpadea todo, animaste mal |
| Emulate prefers-color-scheme | probar dark mode sin cambiar tu SO |
| Toggle device toolbar | móviles reales + zoom 200% para accesibilidad |
Auditoría final de proyecto
- Lighthouse: Performance/A11y/Best Practices ≥ 90.
- Navegación 100% teclado sin perder el foco visual.
- Animaciones esenciales sobreviven a reduced-motion.
- Peso CSS total razonable (<100KB sin comprimir en proyectos medianos).
Puntos clave
- Layout > Paint > Composite en costos; anima composite.
- focus-visible sagrado; reduced-motion respetado.
- Paint flashing delata animaciones mal hechas.
- Auditar con Lighthouse antes de entregar.
34 · Proyecto integrador: página completa responsive
Meta ~50 minUna landing de producto completa usando el manual entero: sistema de variables, grid que muta, flex interno, tipografía fluida, dark mode y animaciones responsables.
El encargo
Landing de «Café Chanchamayo» para Aroma & Punto: hero con CTA, catálogo de 6 productos, sección de reseñas, footer — todo responsive con modo claro/oscuro. Un HTML + un CSS.
1. Sistema (caps. 9/25/31)
:root {
--marca-h: 270;
--marca: hsl(var(--marca-h) 50% 40%);
--fondo: #faf7f2; --texto: #2a2430; --superficie: #fff;
--fs-hero: clamp(2rem, 5vw + .5rem, 3.5rem);
--seccion: clamp(2rem, 6vh, 4rem);
--radio: .75rem;
}
[data-theme="oscuro"] {
--fondo: #17121d; --texto: #ece6f4; --superficie: #241c30;
}
2. Estructura responsiva (caps. 19–24)
body { display: flex; flex-direction: column;
min-height: 100dvh; margin: 0; }
.pagina {
display: grid;
gap: var(--seccion);
padding: var(--seccion);
max-width: 72rem;
margin-inline: auto;
}
@media (min-width: 64rem) {
.pagina { grid-template-columns: 1fr minmax(12rem, 16rem); }
.hero { grid-column: 1 / -1; }
}
3. Catálogo y cards (caps. 14–17/21/26)
.catalogo {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
container-type: inline-size;
}
.card {
background: var(--superficie);
border-radius: var(--radio);
overflow: hidden;
display: flex; flex-direction: column;
transition: translate .25s ease, box-shadow .25s ease;
}
.card:hover {
translate: 0 -.35rem;
box-shadow: 0 1rem 2rem rgb(0 0 0 / .15);
}
.card .info { padding: 1rem; flex: 1;
display: flex; flex-direction: column; }
.card .precio { margin-top: auto; font-weight: bold; }
4. Hero con entrada (caps. 28–30)
@keyframes entrar {
from { opacity: 0; translate: 0 1.5rem; }
}
.hero > * { animation: entrar .5s ease-out both; }
.hero img { animation-delay: .15s; }
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: .01ms !important; }
}
Checklist de entrega
| Área | Criterio | Cap. |
|---|---|---|
| Sistema | una paleta HSL re-tematizable por data-theme | 31 |
| Layout | mobile-first, áreas que mutan en 64rem | 20–24 |
| Componentes | cards sobreviven a cualquier ranura (@container) | 26 |
| Movimiento | solo transform/opacity + reduced-motion respetado | 30/33 |
| A11y | focus-visible vivo, contraste AA, zoom 200% OK | 33 |
| Auditoría | Lighthouse ≥90 ×4 categorías | 33 |