El modelo de caja en CSS: márgenes, bordes, unidades y color

Hasta ahora has cambiado colores y letras. Para colocar cosas en la página hay que entender el modelo de caja, cómo mide CSS cada elemento: todos son cajas con cuatro capas, y casi todos los «¿por qué esto se sale?» y «¿de dónde sale este hueco?» se explican con ellas. Además verás qué unidad usar en cada caso, cómo escribir colores que se lean bien y cómo dejar el texto cómodo.

Todo es una caja

Para CSS, cada elemento de la página es un rectángulo. Un párrafo, un enlace, una imagen, hasta una sola letra dentro de un span. Entender cómo se calcula el tamaño de esa caja es lo que separa «lo muevo hasta que se ve bien» de saber lo que estás haciendo. La caja tiene cuatro capas, de dentro hacia fuera:

Modelo de caja: el contenido en el centro, rodeado por el padding, el borde y, por fuera, el margenLas cuatro capas de una cajamarginpaddingcontenidowidth x heightborder: la banda oscuraCon box-sizing: border-box, width midecontenido + padding + borde, que es lo intuitivo.
Contenido, padding, borde y margen. Esquema: decodigo.com.
  • width, heightEl tamaño del contenido (por omisión; enseguida lo cambiamos).
  • paddingEl relleno entre el contenido y el borde. Se pinta con el fondo del elemento.
  • borderEl borde: grosor, estilo y color, como 2px solid #c9d6e4.
  • marginLa separación con lo de fuera. Siempre es transparente.

Las cuatro aceptan un valor para cada lado. Con un solo valor, vale para los cuatro; con dos, el primero es arriba y abajo y el segundo izquierda y derecha; con cuatro, van en el sentido de las agujas del reloj empezando por arriba: padding: 10px 20px 30px 40px. También hay versiones de un solo lado: margin-top, padding-left…

box-sizing: el tamaño que esperas

<div class="caja contenido">content-box</div>
<div class="caja borde">border-box</div>
.caja {
  width: 200px;
  padding: 20px;
  border: 5px solid #1e64af;
  margin-bottom: 10px;
  background: #eef3f9;
}
.contenido { box-sizing: content-box; }
.borde { box-sizing: border-box; }

Las dos cajas tienen width: 200px, pero la primera mide 250 píxeles de ancho y la segunda 200. Con el valor por omisión, content-box, el ancho es solo el del contenido y el padding y el borde se suman por fuera (200 + 2 × 20 + 2 × 5). Con border-box, el ancho incluye padding y borde, y el contenido se encoge para que cuadre. Es tan cómodo que casi todas las hojas de estilo empiezan con:

*, *::before, *::after {
  box-sizing: border-box;
}

El error típico: width: 100% que se sale

<div class="marco">
  <input class="campo" value="Este campo se sale del marco">
</div>
.marco {
  width: 260px;
  border: 2px dashed #b4bfcb;
}
.campo {
  width: 100%;
  padding: 10px;
  border: 2px solid #1e64af;
}

El campo ocupa el 100 % del marco… más su padding y su borde: se sale 24 píxeles por la derecha. Es el mismo problema de antes; con box-sizing: border-box en la regla del campo (o en la regla universal de arriba) cabe exacto.

Márgenes que se juntan

Los márgenes verticales tienen una regla que sorprende a todo el mundo: cuando el margen de abajo de un elemento toca el margen de arriba del siguiente, no se suman: queda el mayor. Se llama colapso de márgenes.

<p class="uno">Margen de abajo: 30px</p>
<p class="dos">Margen de arriba: 20px</p>
p { margin: 0; background: #eef3f9; }
.uno { margin-bottom: 30px; }
.dos { margin-top: 20px; }

El hueco entre los dos párrafos es de 30 píxeles, no de 50. Tiene su lógica para el texto (un título con margen de 30 y un párrafo con margen de 20 deben quedar a 30, no a 50), pero desconcierta en otros casos. Solo pasa con márgenes verticales y entre bloques normales: dentro de un contenedor Flexbox o Grid (lección 7) no hay colapso. Por eso muchos diseñadores ponen el margen solo hacia abajo, o usan gap.

display: bloques y líneas

La propiedad display decide cómo se coloca la caja respecto a las demás:

  • blockEmpieza en una línea nueva y ocupa todo el ancho disponible. Así son p, h1, div, section…
  • inlineVa dentro de la línea de texto, como una palabra más. Así son a, strong, span. Ignora width y height y los márgenes verticales.
  • inline-blockVa en la línea, pero acepta tamaño y márgenes como un bloque. Útil para botones.
  • noneNo se pinta ni ocupa sitio, como si no existiera.
  • flex, gridConvierten la caja en un contenedor que coloca a sus hijos. Son la lección 7.
<p>Un <span class="a">span inline</span> y un <span class="b">span inline-block</span> en la misma línea.</p>
span { width: 150px; padding: 4px; background: #eef3f9; border: 1px solid #1e64af; }
.b { display: inline-block; }

Los dos tienen width: 150px, pero el primero lo ignora y mide lo que su texto; el segundo, que es inline-block, mide 160 (150 más el padding y el borde).

Unidades

  • pxPíxeles. En CSS un píxel es una unidad fija de referencia, no un punto físico de la pantalla: en un celular de alta densidad, 1 px de CSS son 3 o 4 puntos reales.
  • remMúltiplo del tamaño de letra de la raíz (html), que por omisión es 16 px. 1.5rem = 24 px. Respeta el tamaño de letra que la persona eligió en su navegador: es la mejor unidad para tamaños de letra y espacios.
  • emMúltiplo del tamaño de letra del propio elemento (o del padre, si es para font-size). Útil para que un icono o un padding crezcan con su texto.
  • %Porcentaje de algo del padre; en width, del ancho del contenedor.
  • vw, vh, dvhUn 1 % del ancho o del alto de la ventana. En el celular, dvh se ajusta cuando aparece o desaparece la barra del navegador; vh no.
  • chEl ancho del carácter «0» de la letra actual. max-width: 65ch da líneas de texto de unos 65 caracteres, cómodas de leer.
<div class="n">Nivel 1
  <div class="n">Nivel 2
    <div class="n">Nivel 3</div>
  </div>
</div>
.n { font-size: 1.2em; }

Cuidado con em en font-size: se acumula. Cada nivel mide 1.2 veces el anterior, así que el tercero ya va en 27.65 píxeles. Con rem los tres medirían lo mismo, porque todos se calculan desde la raíz.

Color

CSS acepta varias formas de escribir un color. Todas estas son el mismo azul, salvo la última, que es una forma distinta de describir colores:

color: #1e64af;                 /* hexadecimal: rojo 1e, verde 64, azul af */
color: rgb(30 100 175);          /* rojo, verde y azul de 0 a 255 */
color: rgb(30 100 175 / 50%);    /* con 50 % de opacidad */
color: hsl(211 71% 40%);         /* tono, saturación, luminosidad */
color: oklch(50% 0.13 253);      /* luminosidad percibida, croma y tono */

Los hsl() y oklch() son más fáciles de ajustar a mano: para un azul más claro, sube la luminosidad y deja lo demás. oklch además está pensado para que el mismo número de luminosidad se vea igual de claro en cualquier tono, cosa que no pasa con hsl (un amarillo y un azul con la misma «luminosidad» no se ven igual). Si buscas un color o quieres convertir entre formatos, tienes el conversor de colores.

Contraste

Un texto gris claro sobre blanco se ve elegante y no lo lee casi nadie: ni quien tiene la vista cansada ni quien mira el celular al sol. Las pautas de accesibilidad WCAG piden un contraste de al menos 4.5 a 1 para el texto normal y 3 a 1 para el texto grande. El gris #5b6b7c sobre blanco tiene 5.5 a 1 y pasa; el gris #9aa7b4 tiene 2.5 a 1 y no.

<p class="bien">Gris #5b6b7c: se lee bien (5.5:1).</p>
<p class="mal">Gris #9aa7b4: cuesta leerlo (2.5:1).</p>
.bien { color: #5b6b7c; }
.mal { color: #9aa7b4; }

Las herramientas del navegador muestran el contraste al elegir un color en el panel de estilos, con una marca si no llega al mínimo.

Tipografía

<article>
  <h1>Una buena página de texto</h1>
  <p>El tamaño base de 16 px es el mínimo razonable en pantalla. Con un interlineado de 1.6 y líneas de unos 65 caracteres, el texto se lee sin esfuerzo, también en el celular.</p>
</article>
body {
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: 1rem;
  line-height: 1.6;
  color: #33475b;
}
article {
  max-width: 65ch;
  margin: 0 auto;
}
h1 {
  font-size: 1.75rem;
  line-height: 1.2;
  color: #0f1f33;
}
  • font-family es una lista: el navegador usa la primera fuente que tenga. system-ui es la del sistema operativo (San Francisco en la Mac y el iPhone, Segoe UI en Windows, Roboto en Android): se ve bien y no hay que descargar nada. Termina siempre con una genérica: sans-serif, serif o monospace.
  • line-height va sin unidades (1.6, no 1.6em): así cada elemento lo calcula con su propio tamaño de letra y los títulos grandes no quedan con huecos enormes.
  • Para usar una fuente propia se declara con @font-face y el archivo .woff2, o se enlaza de un servicio como Google Fonts. Cada fuente pesa: dos familias con dos grosores cada una suelen bastar.

Retos

Reto 1. Una caja tiene width: 300px, padding: 15px, border: 3px solid y margin: 10px. ¿Cuánto mide de ancho en pantalla (sin contar el margen) con content-box y con border-box?

Ver la solución
<div class="a">content-box</div>
<div class="b">border-box</div>
div { width: 300px; padding: 15px; border: 3px solid #1e64af; margin: 10px; }
.b { box-sizing: border-box; }

Con content-box, 300 + 2 × 15 + 2 × 3 = 336 píxeles; con border-box, 300. El margen queda fuera en los dos casos.

Reto 2. Haz un botón con un enlace: fondo azul, texto blanco, esquinas redondeadas, sin subrayado y con un relleno que crezca si se agranda la letra.

Ver la solución
<a class="boton" href="#">Descargar</a>
<a class="boton grande" href="#">Descargar</a>
.boton {
  display: inline-block;
  padding: 0.6em 1.2em;
  border-radius: 0.5em;
  background: #1e64af;
  color: white;
  text-decoration: none;
  font-family: system-ui, sans-serif;
}
.grande { font-size: 1.5rem; }

Con em en el padding y el radio, el botón grande crece en proporción sin escribir otra regla. Va como inline-block para que el padding vertical empuje a lo que tiene alrededor.

Reto 3. Tienes dos tarjetas una debajo de otra, con margin: 20px cada una. ¿Cuánto espacio queda entre ellas? ¿Y si las metes en un contenedor con display: flex; flex-direction: column?

Ver la solución
<div class="normal"><div class="t">A</div><div class="t">B</div></div>
<div class="flex"><div class="t">A</div><div class="t">B</div></div>
.t { margin: 20px; padding: 8px; background: #eef3f9; }
.flex { display: flex; flex-direction: column; }

En el flujo normal, 20 píxeles: los márgenes se colapsan. Dentro de Flexbox, 40: ahí no hay colapso y se suman.

Cada fragmento de HTML de esta lección pasó por el Nu Html Checker 26.11.4, el validador oficial del W3C, y cada resultado se pintó en Chromium, Firefox y WebKit (los motores de Chrome, Firefox y Safari); las cifras del texto se midieron en los tres. ¿Quieres probar algo? Usa el editor de HTML y CSS. Logotipos de HTML5 (W3C) y CSS3 (Rudloff) en la imagen de la página: Wikimedia Commons, CC BY 3.0.