Tres horas. Media hora para entender de qué capas se compone una caja y respecto a qué se calcula cada unidad, y dos horas y media construyendo el sistema visual de tu sitio y eliminando de él los valores repetidos.
Hoy · Hoja de ruta
- 1. Aprende: De qué capas se compone una caja, qué cambia
box-sizing, respecto a qué se calcula cada unidad y cómo se declara una decisión de diseño una sola vez. - 2. Haz: Construye las tarjetas del catálogo, define el sistema visual del sitio y conviértelo en variables.
- 3. Comprueba: Ninguna tarjeta desborda al estrechar la ventana, y puedes cambiar el color principal del sitio tocando una sola línea.
Antes de empezar · 5 minutos, sin apuntes
- Si dos reglas cambian el mismo color, ¿qué criterios deciden cuál gana?
- ¿Qué propiedades suelen heredarse?
- Explica por qué añadir
!importantno diagnostica el conflicto.
Se explica
25 minutos · conceptos y demostración
Todo lo de hoy sirve para lo mismo: dejar de escribir números sueltos y empezar a declarar decisiones. Primero la caja, después las unidades y al final las variables que ponen nombre a esas decisiones.
Todo es una caja
┌────────────── margin ───────────────┐
│ │
│ ┌────────── border ───────────┐ │
│ │ │ │
│ │ ┌────── padding ──────┐ │ │
│ │ │ │ │ │
│ │ │ contenido │ │ │
│ │ │ │ │ │
│ │ └─────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────┘ │
│ │
└─────────────────────────────────────┘
| Capa | Qué es | Se ve |
|---|---|---|
| Contenido | El texto o la imagen | Sí |
padding |
Espacio dentro de la caja, entre el contenido y el borde | Toma el fondo de la caja |
border |
El borde | Sí |
margin |
Espacio fuera de la caja, que la separa de las demás | Siempre transparente |
La confusión clásica es padding contra margin. La distinción práctica: el padding aleja el contenido de su propio borde, el margin aleja la caja de sus vecinas. Si pones un fondo de color, el padding se pinta y el margin no.
.card {
padding: 1.5rem; /* aire por dentro */
margin-bottom: 2rem; /* separación de la siguiente */
border: 1px solid #ddd;
border-radius: 0.75rem;
}
El fondo llega hasta el borde: pinta el contenido y el padding, pero nunca el margin.
box-sizing, o por qué 300 no son 300
Por defecto, width mide solo el contenido. El padding y el borde se suman por fuera:
.card {
width: 300px;
padding: 20px;
border: 2px solid;
}
Esa tarjeta ocupa 300 + 20 + 20 + 2 + 2 = 344px. Es la causa del desbordamiento más común de toda la unidad: pones dos cajas del 50 % con padding y ya no caben en una fila.
La solución cabe en tres líneas, y se pone al principio de toda hoja de estilos:
*,
*::before,
*::after {
box-sizing: border-box;
}
border-box
Hace que width incluya el padding y el borde. Ahora 300px son 300px, pase lo que pase por dentro, y el tamaño declarado es el tamaño real.
Anchura: fija, fluida y con techo
.card { width: 300px; } /* frágil: no cabe en un móvil de 320 */
.card { width: 100%; } /* fluida, pero en un monitor grande se estira */
.card {
width: 100%;
max-width: 30rem; /* fluida hasta un límite */
}
La tercera es casi siempre la correcta: ocupa lo que haya, pero no más de lo razonable. Es la primera aparición de una idea que domina la unidad: describir límites en lugar de medidas exactas.
.container {
width: min(90%, 70rem);
margin-inline: auto;
}
margin-inline: auto es la forma moderna de margin-left: auto; margin-right: auto. Y min(90%, 70rem) se lee: «el 90 % del espacio, salvo que eso pase de 70rem, en cuyo caso 70rem». Un contenedor así funciona en un móvil y en un monitor de 34 pulgadas sin una sola media query.
No todo se mide en píxeles
| Unidad | Se calcula respecto a | Para qué la usamos |
|---|---|---|
px |
Nada, es absoluta | Bordes y detalles que no deben escalar |
rem |
El tamaño de fuente raíz del documento | Tamaños de texto y espaciados |
em |
El tamaño de fuente del propio elemento | Espacios que deben crecer con su texto |
% |
Una medida del contenedor, según la propiedad | Anchuras fluidas |
vw / vh |
El ancho y el alto del viewport | Con cuidado, y casi siempre dentro de clamp() |
rem
Root em: una medida relativa al tamaño de fuente del elemento raíz, que por defecto son 16px. Así, 1.5rem son 24px… hasta que alguien cambia el tamaño de letra de su navegador, y entonces todo tu diseño escala con él. Ese es el motivo real para usarlo.
Ahí está la diferencia importante con px: un font-size: 16px ignora la preferencia de quien necesita la letra más grande. Un 1rem la respeta. Frente a em, la regla práctica es rem por defecto, em cuando quieras que algo escale con su propio texto, porque em se acumula al anidar:
.card { font-size: 1.25rem; padding: 1em; } /* padding = 1.25 × 20px */
.card small { font-size: 0.8rem; padding: 1em; } /* padding = 0.8 × ese texto */
Las unidades de viewport tienen una trampa
100vh parece «la altura de la pantalla», y en un móvil no lo es: las barras del navegador aparecen y desaparecen al hacer scroll, así que el valor cambia bajo tus pies y el contenido salta.
Existen svh, lvh y dvh para las variantes pequeña, grande y dinámica. La regla aplicable hoy es más simple: no uses una unidad porque sea moderna, úsala cuando sepas respecto a qué se calcula.
Color y tipografía
color: #1f2937; /* hexadecimal */
color: rgb(31 41 55); /* rojo, verde, azul */
color: hsl(215 28% 17%); /* tono, saturación, luminosidad */
Los tres describen el mismo color. hsl tiene una ventaja práctica cuando construyes una paleta: para conseguir una variante más clara del mismo color solo tienes que subir el último número, sin recalcular nada.
El contraste no es una cuestión de gusto
Un texto gris claro sobre fondo blanco puede parecerte elegante y ser ilegible para bastante gente. El criterio está medido: el texto normal necesita una relación de contraste de al menos 4.5:1 con su fondo, y el texto grande, 3:1.
DevTools lo calcula: al abrir el selector de color de una declaración muestra la relación de contraste y advierte si no alcanza el mínimo. No es necesario estimarlo visualmente.
body {
font-family: system-ui, sans-serif;
font-size: 1rem;
line-height: 1.6;
}
h1 {
font-size: 2.5rem;
font-weight: 700;
line-height: 1.1;
}
En tipografía hay tres decisiones que casi nadie explica:
- La lista de
font-familyes una cadena de respaldo. El navegador usa la primera que tenga disponible.system-uitoma la tipografía del sistema operativo, así que carga instantánea y se ve nativa en cada dispositivo. - El
line-heightsin unidad —1.6, no1.6rem— es lo correcto: al no tener unidad, cada elemento lo multiplica por su tamaño de fuente, así que un titular grande no acaba con el interlineado de un párrafo. - Los titulares llevan menos interlineado que el texto. Un
line-height: 1.6en unh1de 2.5rem deja un hueco enorme entre sus dos líneas. Alrededor de 1.1 es lo habitual.
Los tamaños, además, no se eligen uno a uno: se elige un paso y se multiplica. Una interfaz coherente usa una o dos familias y una escala corta. La variedad tipográfica no es riqueza: casi siempre es falta de decisión.
| Nivel | Tamaño |
|---|---|
| Texto | 1rem |
| h3 | 1.25rem |
| h2 | 1.75rem |
| h1 | 2.5rem |
Una decisión escrita una sola vez
h1 { color: #2563eb; }
button { background: #2563eb; }
a { color: #2563eb; }
Ese color no aparece tres veces: aparece una decisión, escrita tres veces. El día que cambie, hay que acordarse de los tres sitios, y del cuarto que se te olvidó.
:root {
--color-primary: #2563eb;
}
h1 { color: var(--color-primary); }
button { background: var(--color-primary); }
a { color: var(--color-primary); }
Custom property
Una variable de CSS. Se declara con dos guiones, se lee con var(), y se hereda como cualquier otra propiedad: por eso se declaran en :root, que es el elemento raíz, y así están disponibles en todo el documento.
Que se hereden tiene una consecuencia útil: puedes redefinir una variable dentro de un componente y todo lo que haya debajo usará el valor nuevo, sin tocar nada más.
.card--oferta {
--color-primary: #c2410c; /* dentro de esta tarjeta, otro primario */
}
Cuatro funciones que quitan media queries
calc() opera entre unidades distintas, que es lo que ninguna otra cosa puede hacer. Necesita espacios alrededor del - y del +; sin ellos no funciona, y es un despiste que cuesta encontrar.
width: calc(100% - 2rem);
min() y max() se leen al revés de lo que parece: min() pone un techo y max() pone un suelo.
width: min(90%, 70rem); /* nunca pasará de 70rem */
padding: max(1rem, 3vw); /* nunca bajará de 1rem */
clamp() reúne las dos con un valor preferido en medio:
h1 {
font-size: clamp(2rem, 5vw, 4rem);
}
mínimo 2rem · nunca más pequeño
preferido 5vw · lo que se adapta
máximo 4rem · nunca más grande
Ese titular crece con la ventana entre dos límites, sin una sola media query. Es la herramienta con la que se resuelve buena parte del responsive antes de llegar a los breakpoints, y volveremos a ella en la sesión 4.
Se trabaja
150 minutos · práctica sobre tu propio proyecto
Los tres primeros pasos construyen el sistema visual; los tres últimos lo ponen a prueba cambiándolo entero desde un solo sitio.
Paso 1 · Construye tus tarjetas · 30 min
En productos.html tienes article con el marcado de cada producto. Dales forma:
- Pon el
box-sizing: border-boxglobal al principio de tu hoja. - Da a cada tarjeta
padding,border,border-radiusy separación entre ellas. - Limita su anchura con
widthymax-widthen lugar de un valor fijo. - Crea la clase
.containery aplícala para centrar el contenido de las páginas. - Inspecciona una tarjeta en DevTools y localiza en el diagrama de Layout cada una de las cuatro capas. Comprueba que los números coinciden con lo que escribiste.
Consultar · formas de escribir el espaciado y colapso de márgenes
Los valores de padding y margin admiten varias formas:
padding: 1rem; /* las cuatro caras */
padding: 1rem 2rem; /* vertical | horizontal */
padding: 1rem 2rem 3rem 4rem; /* arriba, derecha, abajo, izquierda */
Dos márgenes verticales adyacentes no se suman: se funden en el mayor de los dos. Si un párrafo tiene 20px abajo y el siguiente 30px arriba, la separación es 30, no 50.
Solo pasa en vertical, y no pasa dentro de un contenedor Flexbox o Grid. Por eso, en cuanto empecemos a usar gap, este problema desaparece: es una de las razones por las que gap es preferible a los márgenes para separar elementos de una lista.
Estoy atascado · mis tarjetas desbordan
Casi siempre es una de estas tres, en este orden de probabilidad:
- Falta el
box-sizing: border-box, y el padding se está sumando por fuera delwidth. - Hay un
widthfijo en píxeles mayor que el espacio disponible. - Hay una imagen dentro sin
max-width: 100%, y la imagen empuja la caja desde dentro.
En DevTools, la pestaña Layout te dibuja el box model con sus medidas reales: compara ese número con el que tú escribiste y verás por dónde se va la diferencia.
Antes de continuar: estrecha la ventana hasta 320px. Ninguna tarjeta debe desbordar su contenedor.
Paso 2 · Tu sistema visual mínimo · 35 min
Define para tu proyecto, y aplícalo a las cuatro páginas:
- Familia tipográfica y tamaño base.
- Una escala de encabezados de tres o cuatro pasos.
line-heightpara texto y para titulares.- Color de texto, color de fondo y un color principal.
- Dos o tres medidas de espaciado que vayas a repetir.
Comprueba el contraste de tu color de texto sobre tu fondo con DevTools. Si no llega a 4.5:1, oscurece hasta que llegue.
No buscamos todavía un diseño espectacular. Buscamos consistencia: que las cuatro páginas parezcan del mismo sitio.
Paso 3 · Elimina los números mágicos · 35 min
Número mágico
Un valor escrito a pelo en el código sin que nada explique de dónde sale. margin-left: 37px es el ejemplo perfecto: funciona, nadie sabe por qué, y nadie se atreve a tocarlo.
Sobre tu hoja de estilos:
- Busca los valores que se repiten: colores, espaciados, radios, tamaños.
- Decide cuáles representan una decisión reutilizable y conviértelos en variables con nombres que digan su papel.
- Sustituye todas sus apariciones por
var(). - Prueba a cambiar
--color-primarypor otro color y recarga. Si el sitio entero cambia de color con una sola línea, lo has hecho bien.
Consultar · un sistema pequeño, y el valor de respaldo
:root {
--color-text: #1f2937;
--color-background: #ffffff;
--color-primary: #2563eb;
--space-xs: 0.5rem;
--space-sm: 1rem;
--space-md: 1.5rem;
--space-lg: 3rem;
--radius: 0.75rem;
}
Fíjate en los nombres. --space-sm dice para qué sirve; --rem16 diría solo cuánto mide. Un buen nombre de variable describe el papel, no el valor, porque el valor puede cambiar y el papel no.
Y var() admite un valor de respaldo, útil cuando la variable puede no estar definida:
padding: var(--space-md, 1.5rem);
No conviertas todo en una variable
Un valor que aparece una sola vez y no es una decisión de diseño no gana nada por ser variable: gana una indirección más que leer. Si border-width: 1px solo está en un sitio, déjalo donde está.
La pregunta es: «si esto cambia, ¿tendría que cambiar en otros sitios a la vez?». Si la respuesta es sí, es una variable. Si es no, es un valor.
Paso 4 · Titulares fluidos con clamp() · 15 min
Sustituye el font-size fijo de tus encabezados por un clamp() y comprueba el comportamiento estrechando la ventana desde 1600px hasta 320px.
- Aplica
clamp()alh1y alh2. - Anota a qué anchura deja de crecer y a cuál deja de encoger, comprobándolo en la pestaña Computed.
- Comprueba que el
h1no se come la pantalla en un móvil ni queda diminuto en un monitor grande. - Anota la anchura a la que tu catálogo empieza a verse mal. Guarda ese número: lo necesitarás en la sesión 4.
Paso 5 · Una segunda variante visual · 20 min
Sin tocar una sola regla que no sea una declaración de variable, crea una segunda versión del sitio con otra identidad: otro color principal, otro fondo, otra escala de espaciados.
Guárdala como un bloque :root alternativo comentado al principio de la hoja. Si has tenido que modificar alguna regla fuera de :root, ese valor era una decisión que se te escapó del sistema: conviértelo en variable y vuelve a intentarlo.
Paso 6 · Contraste y coherencia · 15 min
| Comprobación | Resultado | Corrección |
|---|---|---|
| Contraste del texto sobre el fondo (mínimo 4.5:1) | ||
| Contraste del color principal sobre el fondo | ||
| Las cuatro páginas usan la misma familia y la misma escala | ||
No queda ningún color escrito a pelo fuera de :root |
||
| No queda ninguna anchura fija en píxeles |
Ampliación si has completado el trabajo
Primero termina y comprueba los seis pasos. Los dos retos piden diagnosticar y decidir sobre código que no has escrito tú.
Reto 1 · Cuatro cajas que desbordan por cuatro motivos distintos
<div class="fila">
<div class="caja a">Uno</div>
<div class="caja b">Dos</div>
<div class="caja c"><img src="foto.jpg" alt="Una foto"></div>
<div class="caja d">Cuatro palabras bastante largas aquí</div>
</div>
.fila { width: 400px; border: 2px solid red; }
.caja { padding: 20px; border: 1px solid #ccc; }
.a { width: 50%; }
.b { width: 200px; margin-left: 30px; }
.c { width: 100%; }
.c img { width: 500px; }
.d { width: 100%; padding: 60px; }
Las cuatro se salen del contenedor rojo, y cada una por una razón distinta. Para cada caja:
- Calcula a mano cuánto ocupa realmente, sumando contenido, padding y borde.
- Di cuál de los cuatro motivos es el suyo: el modelo de caja por defecto, un margen que se suma a una anchura, un contenido interno más ancho que su caja, o un padding desproporcionado.
- Corrígela sin cambiar el
widthdeclarado de la caja, salvo en el caso en que esa sea la única solución posible. Justifica cuál es ese caso. - Aplica el
box-sizing: border-boxglobal y vuelve a calcular las cuatro. ¿Cuántas quedan arregladas solo con eso? Esa cifra explica por qué esas tres líneas abren toda hoja de estilos.
Reto 2 · Qué merece ser variable
Esta hoja pertenece a un proyecto ajeno. Decide qué valores se convierten en custom properties y cuáles se quedan donde están.
.header { background: #0f172a; padding: 24px 32px; border-bottom: 1px solid #1e293b; }
.header h1 { color: #f8fafc; font-size: 28px; letter-spacing: -0.02em; }
.nav a { color: #94a3b8; margin-right: 24px; }
.nav a:hover { color: #f8fafc; }
.card { background: #f8fafc; padding: 24px; border-radius: 12px; }
.card h3 { color: #0f172a; font-size: 20px; }
.card .precio { color: #0f172a; font-size: 28px; font-weight: 700; }
.boton { background: #2563eb; color: #f8fafc; padding: 12px 24px; border-radius: 12px; }
.pie { background: #0f172a; color: #94a3b8; padding: 32px; }
- Haz el recuento: cuántas veces aparece cada color y cada medida.
- Separa lo que es una decisión repetida de lo que solo es una coincidencia. Dos valores iguales por casualidad no son la misma decisión, y unirlos bajo una variable crea un acoplamiento falso: el día que uno cambie, arrastrará al otro.
- Escribe el
:rooty reescribe la hoja. Los nombres deben describir el papel. - Justifica dos valores que hayas dejado sin convertir en variable, y uno que hayas dudado.
- Cambia el tema entero a una versión clara modificando solo el
:root. Anota qué te ha obligado a volver atrás.
:root, y la tabla de contraste y coherencia contestada.Cierre
5 minutos · comprobación y recuerdo
Lista de verificación de la sesión
- Tu hoja empieza con el
box-sizing: border-boxglobal. - Tienes un bloque
:rootcon colores y espaciados nombrados por su papel. - Puedes cambiar el color principal del sitio tocando una línea.
- Tus anchuras usan
max-widthomin(), no medidas fijas. - Las cuatro páginas comparten tipografía, escala y paleta.
- El contraste del texto sobre el fondo llega al menos a 4.5:1.
Antes de cerrar · 3 minutos, sin mirar
- ¿Cuál es la diferencia práctica entre
paddingymargin? - ¿Qué cambia exactamente
box-sizing: border-box? - ¿Respecto a qué se calcula un
rem, y por qué importa? - ¿Por qué el
line-heightse escribe sin unidad? - ¿Por qué las variables se declaran en
:root? - ¿Qué hace
min(90%, 70rem), en una frase?
Ver respuestas
1 · El padding es espacio dentro de la caja y se pinta con su fondo; el margin es espacio fuera y siempre es transparente.
2 · Que width incluya el padding y el borde, en vez de medir solo el contenido.
3 · Respecto al tamaño de fuente del elemento raíz. Importa porque respeta la preferencia de tamaño de letra de quien usa la web, cosa que un valor en píxeles ignora.
4 · Para que cada elemento lo multiplique por su propio tamaño de fuente en lugar de heredar una altura fija.
5 · Porque las custom properties se heredan, y :root es el elemento raíz: declarándolas ahí quedan disponibles en todo el documento.
6 · Ocupa el 90 % del espacio, pero sin pasar nunca de 70rem. Pone un techo.
Microprueba semanal 2 · 5–10 minutos
Individual, sin IA y sin apuntes.
- Dibuja y nombra las cuatro capas del box model.
- Explica cuándo elegirías
rem,%y un píxel. - Convierte tres colores repetidos en una custom property y predice el efecto de cambiarla.