← CSS: diseño, maquetación y responsive

Sesión 2

La caja y el sistema visual

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. 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. 2. Haz: Construye las tarjetas del catálogo, define el sistema visual del sitio y conviértelo en variables.
  3. 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

  1. Si dos reglas cambian el mismo color, ¿qué criterios deciden cuál gana?
  2. ¿Qué propiedades suelen heredarse?
  3. Explica por qué añadir !important no 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;
}
Vista previaLas cuatro capas del box model
margin · separación exterior
border
padding · aire interior
contenido

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-family es una cadena de respaldo. El navegador usa la primera que tenga disponible. system-ui toma la tipografía del sistema operativo, así que carga instantánea y se ve nativa en cada dispositivo.
  • El line-height sin unidad —1.6, no 1.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.6 en un h1 de 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:

  1. Pon el box-sizing: border-box global al principio de tu hoja.
  2. Da a cada tarjeta padding, border, border-radius y separación entre ellas.
  3. Limita su anchura con width y max-width en lugar de un valor fijo.
  4. Crea la clase .container y aplícala para centrar el contenido de las páginas.
  5. 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:

  1. Falta el box-sizing: border-box, y el padding se está sumando por fuera del width.
  2. Hay un width fijo en píxeles mayor que el espacio disponible.
  3. 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:

  1. Familia tipográfica y tamaño base.
  2. Una escala de encabezados de tres o cuatro pasos.
  3. line-height para texto y para titulares.
  4. Color de texto, color de fondo y un color principal.
  5. 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:

  1. Busca los valores que se repiten: colores, espaciados, radios, tamaños.
  2. Decide cuáles representan una decisión reutilizable y conviértelos en variables con nombres que digan su papel.
  3. Sustituye todas sus apariciones por var().
  4. Prueba a cambiar --color-primary por 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.

  1. Aplica clamp() al h1 y al h2.
  2. Anota a qué anchura deja de crecer y a cuál deja de encoger, comprobándolo en la pestaña Computed.
  3. Comprueba que el h1 no se come la pantalla en un móvil ni queda diminuto en un monitor grande.
  4. 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:

  1. Calcula a mano cuánto ocupa realmente, sumando contenido, padding y borde.
  2. 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.
  3. Corrígela sin cambiar el width declarado de la caja, salvo en el caso en que esa sea la única solución posible. Justifica cuál es ese caso.
  4. Aplica el box-sizing: border-box global 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; }
  1. Haz el recuento: cuántas veces aparece cada color y cada medida.
  2. 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.
  3. Escribe el :root y reescribe la hoja. Los nombres deben describir el papel.
  4. Justifica dos valores que hayas dejado sin convertir en variable, y uno que hayas dudado.
  5. Cambia el tema entero a una versión clara modificando solo el :root. Anota qué te ha obligado a volver atrás.
Objetivo mínimoTarjetas que no desbordan a 320px, sistema visual aplicado a las cuatro páginas y variables con nombres semánticos.
Si lo tienesLa segunda variante visual funcionando cambiando solo el :root, y la tabla de contraste y coherencia contestada.
RetoLas cuatro cajas diagnosticadas con su cálculo a mano, y la hoja ajena convertida en sistema con las decisiones justificadas.

Cierre

5 minutos · comprobación y recuerdo

Lista de verificación de la sesión

  • Tu hoja empieza con el box-sizing: border-box global.
  • Tienes un bloque :root con colores y espaciados nombrados por su papel.
  • Puedes cambiar el color principal del sitio tocando una línea.
  • Tus anchuras usan max-width o min(), 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

  1. ¿Cuál es la diferencia práctica entre padding y margin?
  2. ¿Qué cambia exactamente box-sizing: border-box?
  3. ¿Respecto a qué se calcula un rem, y por qué importa?
  4. ¿Por qué el line-height se escribe sin unidad?
  5. ¿Por qué las variables se declaran en :root?
  6. ¿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.

  1. Dibuja y nombra las cuatro capas del box model.
  2. Explica cuándo elegirías rem, % y un píxel.
  3. Convierte tres colores repetidos en una custom property y predice el efecto de cambiarla.