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.

34 capítulos Bootstrap 5.3 Modo claro / oscuro Optimizado para móvil CSS Snapshot · W3C
34
Capítulos
150+
Ejemplos de código
3
Niveles: básico a experto
0
Requisitos previos
Cómo usar este tutorial: sigue los capítulos en orden (el índice está en el menú si lees desde el móvil). Cada capítulo tiene teoría, ejemplos ejecutables y puntos clave al final. Practica cada ejemplo: es la única vía para dominar cualquier tema.

1 · Qué es CSS y cómo piensa el navegador

Básico ~11 min

CSS 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íaResponde a…
EstructuraHTML¿QUÉ es esta página?
PresentaciónCSS¿CÓMO se ve y se acomoda?
ComportamientoJavaScript¿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

  1. Parsea tu HTML → construye el DOM (árbol, eco js_01).
  2. Parsea tu CSS → construye el CSSOM: cada regla clasificada por selector y peso.
  3. Cruza ambos: para CADA nodo resuelve «qué reglas le aplican, cuál gana» — el tema de los caps. 5 y 32.
  4. 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)

FormaEjemploVeredicto
inline (atributo style)<p style="color:red">solo para pruebas puntuales: gana a todo (cap. 5), imposible de reutilizar
<style> embebidobloque en el headvá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
Puente conocido: el mapa head/body del manual de HTML (cap. 2) ya te dijo dónde vive el CSS — en el head vía link. Aquí aprendes qué hay DENTRO de ese archivo.

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 min

Un 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&eacute; 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

ReglaEjemplo
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 espaciospropiedades anidadas visualmente bajo el selector
una propiedad por líneafácil de diffear en git y de leer en reviews

Prueba de humo: ¿mi CSS llega?

body {
  background: #ffe600;   /* amarillo imposible de ignorar */
}
  1. Amarillo visible → tu pipeline HTML→CSS funciona; quita la línea.
  2. 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 min

El selector decide A QUIÉN le hablas. Dominarlos es dominar la mitad de CSS.

Los cuatro simples

SelectorNombreCuándo usarlo
pelementoestilos base globales del tag
.tarjetaclaseel caballo de batalla: reutilizable, sin peso extra (cap. 5)
#cabeceraidcasi nunca en CSS: único y MUY pesado; para anclas/JS mejor
*universalresets 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&eacute;s</a></li>
      </ul>
    </li>
    <li><a href="#">Contacto</a></li>
  </ul>
</nav>
SelectorLee «…»Toma en el ejemplo
.menu uldescendiente (cualquier profundidad)Ambas listas
.menu > ulhijo DIRECTOSolo la lista exterior
li + lihermano inmediato siguientelos li 2.º en adelante — separadores de menú
h2 ~ phermanos 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%;
}
El espacio lo cambia TODO: 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 min

Estados, posiciones y atributos sin escribir una línea de JavaScript ni una clase extra en el HTML.

Selectores de atributo

SelectorCoincide 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); }
  • :hover solo 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

SelectorQué toma
li:first-child / :last-childprimero/ú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 min

Dos 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

  1. Origen e importancia: !important del usuario > !important del autor > estilos normales del autor (los tuyos).
  2. Especificidad: más específico gana.
  3. 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

  1. F12 → clic derecho al elemento → Inspect.
  2. Panel Styles: verás TODAS las reglas candidatas; las perdedoras TACHADAS.
  3. Pasa el mouse sobre el nombre del selector: DevTools muestra SU tripleta.
  4. ¿Tu regla ni aparece? El SELECTOR no coincide (cap. 3–4), no es cascada.
Mirada adelante: cuando esto se vuelva complejo, CSS moderno ofrece @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 min

Por 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 heredaNO se hereda
color, font-*, line-height, text-alignborder, margin, padding, background
visibility, cursorwidth/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

UnidadRelativa a…Úsala para…
pxnada: fijobordes, sombras, detalles de 1–3 px
emfont-size DEL PROPIO elementopadding/margins escalables dentro de texto (¡cuidado: se acumula!)
remfont-size de la raíz (16px default)TODO lo demás: espaciados, tamaños consistentes
%contenedor padreanchos fluidos simples
vw / vhviewport completoheros full-screen; ojo móvil con vh (100dvh moderno)
chancho 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 min

Cada 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-sizingwidth mide…Ancho real de .tarjeta
content-box (default histórico)solo el contenido300 + 40 + 10 = 350px: «¡puse 300 y se agrandó!»
border-boxbordes incluidos300px 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

  1. F12 → clic derecho al elemento → Inspect.
  2. Bajo el panel Styles: diagrama concéntrico margin/border/padding/content con los números REALES calculados.
  3. 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 min

Dos 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

CasoQué pasa
hermanosbottom de uno + top del otro → gana el mayor
padre-hijoel margin-top del PRIMER hijo «se escapa» y empuja al PADRE entero (¡clásico!)
caja vacíaun 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ónEjemploNota
padding en vez de marginpadre con padding-topel padding NUNCA colapsa
interponer algoborder o padding mínimo en el padreaunque sea 1px transparente
overflow distinto de visibleoverflow: clip en el padremínima invasión moderna
cambiar de contextoflex/grid en el padrelos 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 min

Cuatro sintaxis, una recomendada para trabajar, y gradientes que sustituyen imágenes pesadas.

Las sintaxis y para qué sirve cada una

FormatoEjemploFortaleza
hex#663399compacto; el estándar de los design tokens
rgbrgb(102 51 153 / .8)familiar desde JS (eco js_01)
hslhsl(270 50% 40%)razonable por humanos: tono, saturación, luminosidad
nombradorebeccapurpleprototipos 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 min

El 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-displayComportamiento
swapmuestra la fallback YA y cambia al cargar: recomendado
blocktexto invisible hasta 3s: el «FOIT» que castiga LCP
optionalsolo 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

PropiedadUso honesto
font-weight400 normal · 600/700 énfasis; NO uses bold como decoración general
letter-spacingtítulos grandes ligeramente negativo (-.02em); MAYÚSCULAS pequeñas positivo (.08em)
text-transformmayúsculas visuales sin cambiar el HTML real (los lectores leen el original)
fuente variableun archivo, todos los pesos: font-weight: 550 válido
Presupuesto: máximo 2 familias y 3–4 pesos. Cada peso es una descarga completa. Inter 400+700 cubre el 90% de proyectos.

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 min

Alineació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&iacute;tulo largu&iacute;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-spaceEfecto
normalcolapsa espacios y salta línea (default)
nowrapsin saltos de línea: base del truncado
prerespeta 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 min

Las 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)

borderoutline
ocupa espacio del layoutNO ocupa espacio: se dibuja encima
por lado configurablesolo completo
decorativoel 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 min

Antes 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

displayAncho/alto propiosVive junto a…
blockSÍ respeta ambosnadie: línea propia
inlineNO respeta ningunotexto (width/height ignorados)
inline-blockSÍ respeta amboshermanos 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: flex es azúcar de block flex; inline-flex, de inline 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écnicaOcupa espacioFoco/lectores
display:nonenoinaccesible: desaparece TODO
visibility:hiddensí (invisible)también inaccesible
opacity:0AÚN recibe foco y clics: trampa clásica

El puente hacia flexbox

  1. Hijos block apilan; necesitas filas → margin negativos y floats eran el infierno antiguo.
  2. Un solo display:flex en el padre cambia el formato interno: los hijos se acomodan en EJE que tú eliges.
  3. 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 min

Una 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

ValorMain axisUso típico
row (default)horizontal →barras, filas de cards
columnvertical ↓páginas enteras, listas verticales
row-reverse / column-reverseinvertido ←/↑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;
}
Default traicionero: nowrap aplasta a los hijos hasta encogerlos. Casi siempre quieres 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&amp;Punto</a>
  <a href="#">Caf&eacute;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 min

Tres propiedades cubren TODOS los alineamientos posibles. El truco: identificar en qué eje piensa cada una.

El mapa de las tres propiedades

PropiedadEje que gobiernaActúa sobre…
justify-contentMAIN (corren)todos los hijos como grupo
align-itemsCROSScada hijo individual
align-contentCROSSlas 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&eacute; 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 min

flex-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

PropiedadControlaDefault
flex-growcómo reparte el ESPACIO SOBRANTE0 (no crece)
flex-shrinkcómo REPARTE el déficit cuando falta1 (todos encogen)
flex-basistamaño de PARTIDA en el main axisauto (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

ShorthandEquivale a…Léase como…
flex: 11 1 0%parte igualado desde CERO: todas las columnas idénticas
flex: auto1 1 autocrece pero respeta el contenido como base
flex: none0 0 autotamaño sagrado: ni crece ni encoge (iconos)
flex: 0 0 12remfijosidebar 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 */
flex:1 vs flex:auto — la diferencia sutil: con basis 0% las columnas son IGUALES aunque su texto difiera; con auto, cada una parte de su contenido. Para grillas uniformes usa flex:1.

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 min

Cuatro 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

  1. ¿Quién es el contenedor? Marca display:flex en el PADRE, no en los hijos.
  2. ¿Qué eje necesitas? row/column decide TODO lo demás.
  3. ¿Quién crece? Un solo hijo con flex:1 suele bastar.
  4. ¿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 min

Los 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

  1. F12 → selecciona el contenedor → badge «flex» clicable: DevTools dibuja main/cross axis sobre la página real.
  2. Hermanos con outline temporal distinto por color: quién mide qué.
  3. 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 min

Si flexbox organiza una DIMENSIÓN, grid dibuja la hoja cuadriculada completa ANTES de colocar nada.

El salto conceptual

FlexboxGrid
Diseñas…el CONTENIDO fluye y empujala REJILLA primero, huecos incluidos
Ejesuno principal (+ cruzado)FILAS y COLUMNAS simultáneas
Ideal para…componentes, barras, filaspá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 min

Dos 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">&iexcl;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 min

La 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-fillmantiene los huecos vacíos: cards del ancho normal, alineadas a la izquierda
auto-fitCOLAPSA 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 min

No 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?

PreguntaHerramienta
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

NecesidadElegir
Navbar, toolbar, fila de botonesflex + margin-auto
Cards alineadas en filas responsivasgrid auto-fill/minmax
Alinear un icono junto a textoflex align-items:center
Layout de página con lateralesgrid areas / minmax+1fr
Sobreponer elementos sin absolutegrid celda compartida 1/1
Empujar un elemento al fondo de una tarjetaflex 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 min

La 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

QueryPara 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)
printhoja 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 min

Diseñ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 abajoy vas AÑADIENDO complejidad arriba
móvil hereda parches y sobrepesosdesktop 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 min

Tipografí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

No
tamaños tipográficoscolores, bordes de 1px
paddings verticales de seccionesanchos de layout estructural (eso es grid)
gaps grandes entre bloquesvalores 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 */
No abuses del vw puro: 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 min

El 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, navsdiseño MICRO: cards, widgets, formularios
una sola verdad por páginacada 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 min

Todo 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

  1. ¿El HTML base es móvil puro sin parches?
  2. ¿Los breakpoints nacieron de romper TU contenido?
  3. ¿Las cards sobreviven a cualquier ranura gracias a @container?
  4. ¿Tipografía y espacios respiran con clamp sin saltos?
  5. ¿Probaste con zoom de navegador al 200%? (eco accesibilidad)
Puente: esta página es la base visual perfecta para la SPA del manual de JS y para consumir la API del curso PHP — misma arquitectura, tres capas.

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 min

El 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

TipoSensación
linearrobótico: solo para loops infinitos
ease (default)vale, pero genérico
ease-outfrena al llegar: entradas de UI
ease-inarranca 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 */
}
display:none no transiciona: desaparece de golpe porque no hay estado intermedio renderizable. Usa opacity+visibility (el combo anterior) o grid-template-rows 0fr→1fr para acordeones puros.

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 min

Donde 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

PiezaEjemploNota
nombreentrarel @keyframes definido
duración.4sprimera marca de tiempo
timingease-outigual que transiciones
iteracióninfinite / 3para loops
directionalternateida y vuelta: pulso natural
fill-modebothmantiene 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 min

Mover, 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 min

La 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
Dónde NO: las variables no sirven dentro de media queries como condiciones (@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 min

Cuando 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>
PiezaSintaxisEjemplo
Bloquepalabra.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 min

El 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

  1. Layout: recalcular geometría (width, top, flex/grid changes) — el más caro.
  2. Paint: repintar píxeles (color, background, box-shadow).
  3. 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

HerramientaResuelve…
Styles panel tachadoquién venció a quién (cascada cap. 5)
Badge grid/flex clicabledibuja ejes y líneas sobre tu página real
Rendering → Paint flashingpinta verde lo que se REPINTA: si parpadea todo, animaste mal
Emulate prefers-color-schemeprobar dark mode sin cambiar tu SO
Toggle device toolbarmóviles reales + zoom 200% para accesibilidad

Auditoría final de proyecto

  1. Lighthouse: Performance/A11y/Best Practices ≥ 90.
  2. Navegación 100% teclado sin perder el foco visual.
  3. Animaciones esenciales sobreviven a reduced-motion.
  4. 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 min

Una 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

ÁreaCriterioCap.
Sistemauna paleta HSL re-tematizable por data-theme31
Layoutmobile-first, áreas que mutan en 64rem20–24
Componentescards sobreviven a cualquier ranura (@container)26
Movimientosolo transform/opacity + reduced-motion respetado30/33
A11yfocus-visible vivo, contraste AA, zoom 200% OK33
AuditoríaLighthouse ≥90 ×4 categorías33
Cierre: ya no «usas CSS»: lo DISEÑAS. Tu HTML del manual anterior tiene su capa visual completa, tu JS la manipulará con clases y variables leídas por getComputedStyle, y tus proyectos PHP la servirán. Siguiente parada natural: preprocesadores o Tailwind cuando el equipo crezca. La web se ve como tú decidas.