Las rarezas de CSS y HTML: catorce comportamientos que parecen errores y no lo son: por qué ocurren y cómo evitarlos. Cada uno lleva un ejemplo en vivo y su resultado medido en los tres motores de navegador.
CSS tiene fama de caprichoso, y casi siempre es injusta: casi todo lo que parece un error del navegador es una regla que tiene su razón, a menudo histórica. Aquí están las catorce que más desconciertan, con un ejemplo en vivo de cada una, lo que pasa de verdad (medido en Chrome, Firefox y Safari, no a ojo), por qué y cómo evitarlo. Si vienes del curso de HTML y CSS, varias ya te sonarán.
- El margen del hijo se sale del padre
- El hueco bajo las imágenes
- El espacio misterioso entre elementos en línea
- height: 100% que no hace nada
- Un z-index de 9999 que pierde contra uno de 2
- Un elemento flex que no se encoge
- Un párrafo que se cierra solo
- chucknorris es un color (rojo)
- El padding vertical en % sale del ancho
- position: sticky que no se pega
- Un botón que envía el formulario sin querer
- !important al revés en las capas
- Una clase que empieza por número no funciona
- margin: auto no centra en vertical
1. El margen del hijo se sale del padre
<div class="caja"> <h2>Título con margen de arriba</h2> </div>
body { margin: 0; }
.caja { background: #eef3f9; }
h2 { margin-top: 40px; }Esperarías un fondo azul claro con 40 píxeles de aire encima del título. Lo que pasa: el fondo empieza 40 píxeles más abajo de lo esperado y el título queda pegado a su borde superior (0 píxeles). El margen del hijo atraviesa al padre y se queda fuera.
Por qué: es el colapso de márgenes de la lección 6, pero entre padre e hijo. Si el padre no tiene borde, padding ni nada que separe los dos márgenes, se funden en uno solo, que se aplica fuera del padre. Cómo evitarlo: display: flow-root en el padre (creado justo para esto), un padding o un borde, o convertir al padre en contenedor flex o grid.
2. El hueco bajo las imágenes
<div class="marco"><img src="https://decodigo.com/wp-content/uploads/2026/10/decodigo_placa_raspberry-pi-zero-2-w.jpg" alt="" width="160" height="105"></div> <div class="marco"><img class="bloque" src="https://decodigo.com/wp-content/uploads/2026/10/decodigo_placa_raspberry-pi-zero-2-w.jpg" alt="" width="160" height="105"></div>
.marco { background: #b91c1c; width: 160px; margin-bottom: 8px; }
.bloque { display: block; }La imagen mide 105 píxeles de alto, pero el primer marco, con fondo rojo para que se vea, mide más: asoma una franja roja de 4 a 5 a 6 píxeles por debajo (depende de la letra de cada navegador). El segundo, con la imagen en display: block, mide exactamente 105.
Por qué: una imagen es, por omisión, un elemento en línea, como una letra, y se apoya en la línea base del texto. Debajo de la línea base queda sitio para los rabos de letras como la g o la p, aunque no haya ningún texto. Cómo evitarlo: img { display: block; } en tu hoja base, o vertical-align: middle si la imagen tiene que ir en una línea de texto.
3. El espacio misterioso entre elementos en línea
<div class="fila"> <span>Uno</span> <span>Dos</span> <span>Tres</span> </div>
.fila { width: 240px; background: #eef3f9; }
.fila span { display: inline-block; width: 80px; background: #1e64af; color: white; text-align: center; }Tres cajas de 80 píxeles en un contenedor de 240 deberían caber justas. No caben: entre ellas aparece un hueco y la tercera baja a otra línea (el contenedor ocupa 2 líneas en los tres navegadores).
Por qué: el salto de línea y los espacios del HTML entre </span> y <span> son texto, y entre elementos en línea se pintan como un espacio. Cómo evitarlo: no uses inline-block para maquetar; usa display: flex en el contenedor, donde los espacios del código no cuentan, y separa con gap.
4. height: 100% que no hace nada
<div class="padre"> <div class="hijo">Quiero ocupar todo el alto</div> </div>
.padre { background: #eef3f9; min-height: 150px; }
.hijo { height: 100%; background: #1e64af; color: white; }El padre mide 150 píxeles de alto, pero el hijo, con height: 100%, solo mide lo que su texto (¿llena al padre? false).
Por qué: un porcentaje de altura se calcula sobre la altura definida del padre, y aquí el padre no tiene height, solo min-height: su altura depende de su contenido, que a su vez depende del hijo. Para no entrar en un círculo, el navegador trata el 100 % como auto. Cómo evitarlo: haz al padre contenedor flex o grid; sus hijos se estiran solos a toda la altura (align-items: stretch es el valor por omisión).
5. Un z-index de 9999 que pierde contra uno de 2
<div class="tarjeta"> <div class="menu">Menú con z-index: 9999</div> </div> <div class="cabecera">Cabecera con z-index: 2</div>
.tarjeta { position: relative; z-index: 1; }
.menu { position: absolute; top: 0; left: 0; z-index: 9999; width: 220px; height: 90px; background: #1e64af; color: white; padding: 8px; }
.cabecera { position: relative; z-index: 2; margin-top: 40px; height: 60px; background: #b91c1c; color: white; padding: 8px; }El menú tiene z-index: 9999 y la cabecera, 2. Donde se enciman, el elemento de arriba es… la cabecera, en los tres navegadores.
Por qué: el z-index no es global. Su padre, .tarjeta, tiene position: relative y z-index: 1, y eso crea un contexto de apilamiento: todo lo de dentro se ordena entre sí, y luego la tarjeta entera compite con el resto como si fuera un 1. Contra el 2 de la cabecera, pierde, con su 9999 dentro. También crean contexto opacity menor que 1, transform, filter e isolation: isolate. Cómo evitarlo: busca qué ancestro crea el contexto (las herramientas del navegador lo indican en Chrome) y sube ahí el z-index, o saca el menú fuera.
6. Un elemento flex que no se encoge
<div class="fila"> <div class="lado">Lado</div> <div class="texto">supercalifragilisticoespialidosoysinespacios</div> </div>
.fila { display: flex; width: 260px; border: 2px dashed #b4bfcb; }
.lado { width: 80px; flex: none; background: #eef3f9; }
.texto { flex: 1; background: #1e64af; color: white; }El texto debería ocupar lo que queda (flex: 1) dentro de los 260 píxeles. En cambio, se sale de la fila (¿se sale? true).
Por qué: los elementos flex y grid tienen min-width: auto, que significa «no me hagas más chico que mi contenido más ancho que no se puede partir». Una palabra larga, una URL o un bloque de código son contenido que no se parte. Cómo evitarlo: min-width: 0 en el elemento (en una rejilla, minmax(0, 1fr) en vez de 1fr) y overflow-wrap: anywhere si el texto debe partirse.
7. Un párrafo que se cierra solo
<p class="aviso">Texto del aviso <div>Un bloque dentro del párrafo</div> </p>
.aviso { border: 2px solid #1e64af; }Nu Html Checker (validador del W3C)línea 3: error: No “p” element in scope but a “p” end tag seen.
Parece un párrafo con un bloque dentro, y el borde azul debería rodear las dos cosas. Pero el borde solo rodea «Texto del aviso». El navegador construyó otra cosa: el párrafo tiene 0 hijos y en el documento hay 2 párrafos, aunque escribiste uno.
Por qué: un p solo puede contener contenido «en línea» (texto, enlaces, strong…). Al encontrar el <div>, el analizador de HTML cierra el párrafo por su cuenta; y el </p> final, que ya no tiene pareja, crea un párrafo vacío nuevo. Cómo evitarlo: usa un div como contenedor, o pasa el código por el validador, que lo detecta (y explica la mitad de los «mi CSS no se aplica» con selectores como p > div).
8. chucknorris es un color (rojo)
<p><font color="chucknorris">Chuck Norris</font></p> <p><font color="mr t">Mr T</font></p>
En los atributos de color antiguos de HTML (color, bgcolor), una palabra cualquiera se convierte en un color: «chucknorris» sale rgb(192, 0, 0), un rojo, y «mr t», rgb(0, 0, 0).
Por qué: los navegadores de los noventa interpretaban cualquier texto como si fuera hexadecimal: cambian por 0 lo que no es un dígito hexadecimal, rellenan con ceros, parten en tres trozos para el rojo, el verde y el azul y se quedan con los dos primeros dígitos de cada uno. La especificación de HTML conservó el algoritmo para no romper páginas viejas. Cómo evitarlo: no uses esos atributos (son obsoletos; el validador marca font como error). En CSS, un color que no existe se ignora y ya.
9. El padding vertical en % sale del ancho
<div class="marco"><div class="caja">padding-top: 50%</div></div>
.marco { width: 300px; height: 80px; border: 2px dashed #b4bfcb; }
.caja { padding-top: 50%; background: #eef3f9; }Un padding-top: 50% en un contenedor de 300 de ancho y 80 de alto sale de 150 píxeles: la mitad del ancho, no del alto, y se desborda del contenedor.
Por qué: los porcentajes de padding y margin, también los verticales, se calculan sobre el ancho del contenedor. Así se hacían las cajas con proporción fija (un video 16:9 era padding-top: 56.25%). Cómo evitarlo: para proporciones, hoy existe aspect-ratio: 16 / 9; para espacios, usa rem.
10. position: sticky que no se pega
<div class="panel">
<div class="envoltura">
<h3 class="fijo">Cabecera pegajosa</h3>
<p>Contenido</p><p>Contenido</p><p>Contenido</p><p>Contenido</p><p>Contenido</p><p>Contenido</p><p>Contenido</p><p>Contenido</p>
</div>
</div>.panel { height: 160px; overflow-y: auto; border: 2px solid #c9d6e4; }
.envoltura { overflow: hidden; }
.fijo { position: sticky; top: 0; margin: 0; padding: 6px; background: #1e64af; color: white; }Al bajar por el panel, la cabecera debería quedarse arriba. Se va con el resto (comprobado: tras bajar 120 píxeles, su borde de arriba queda fuera del panel). Si a la envoltura se le quita overflow: hidden, se queda pegada a 2 píxeles del borde.
Por qué: sticky se pega dentro del ancestro más cercano que tenga desplazamiento, y overflow: hidden (o auto, o scroll) convierte a la envoltura en ese ancestro, aunque ella misma no se desplace. La cabecera se pega a la envoltura, que se va entera. Cómo evitarlo: busca un overflow entre el elemento y el contenedor que se desplaza; si necesitas recortar sin crear un contenedor de desplazamiento, usa overflow: clip.
11. Un botón que envía el formulario sin querer
<form>
<label for="cl">Contraseña</label>
<input id="cl" name="clave" type="password" value="secreta">
<button onclick="var c = document.getElementById('cl'); c.type = c.type === 'password' ? 'text' : 'password'">Mostrar</button>
</form>Pulsa «Mostrar»: en vez de enseñar la contraseña, el formulario se envía (el marco te lo dice). El botón es de tipo submit, aunque nadie lo escribió.
Por qué: dentro de un formulario, un <button> sin type es de tipo submit. Cómo evitarlo: escribe siempre type="button" en los botones que no envían. Lo viste en la lección 4.
12. !important al revés en las capas
<p id="prueba">¿De qué color soy?</p>
@layer base, tema;
@layer base {
p { color: #b91c1c !important; }
}
@layer tema {
p { color: #1e64af !important; }
}Sin !important, la capa tema, declarada después, ganaría. Con !important en las dos, gana base: el texto sale rgb(185, 28, 28), rojo.
Por qué: es a propósito. Las declaraciones !important invierten el orden de las capas para que una capa base pueda proteger algo (un estilo de accesibilidad, por ejemplo) contra todo lo que venga después. Cómo evitarlo: con capas casi nunca hace falta !important; ordena bien las capas en la primera línea.
13. Una clase que empieza por número no funciona
<p class="2columnas">Debería salir en azul</p>
.2columnas { color: #1e64af; }El HTML es válido (en HTML una clase puede empezar por un número; el validador del W3C solo protesta por el CSS), pero la regla no se aplica: el texto sale rgb(0, 0, 0). Y en JavaScript, document.querySelector(".2columnas") lanza un error SyntaxError.
Por qué: en CSS, un identificador no puede empezar por un dígito, así que .2columnas no es un selector válido y el navegador descarta la regla entera sin avisar. Cómo evitarlo: empieza los nombres de clase por una letra (.columnas-2). Si heredaste una clase así, se puede escapar: .\32 columnas, donde \32 es el código del «2».
14. margin: auto no centra en vertical
<div class="marco"><div class="caja">margin: auto</div></div> <div class="marco flex"><div class="caja">margin: auto (en flex)</div></div>
.marco { height: 120px; background: #eef3f9; margin-bottom: 8px; }
.caja { width: 180px; margin: auto; background: #1e64af; color: white; padding: 8px; }
.flex { display: flex; }En el primer marco, la caja se centra en horizontal pero se queda pegada arriba (0 píxeles del borde). En el segundo, que es flex, la misma regla la centra en las dos direcciones.
Por qué: en el flujo normal, margin: auto reparte el espacio sobrante solo en horizontal; en vertical, un margen auto vale 0. Dentro de un contenedor flex o grid, los márgenes automáticos absorben el espacio libre en los dos ejes. Cómo evitarlo: para centrar en vertical, usa Flexbox o Grid en el padre (centrar un elemento tiene todas las formas).
Y una más: 100vh en el celular
En el celular, un elemento con height: 100vh suele salir más alto que la pantalla y obliga a desplazarse. La unidad vh se calcula con la ventana en su tamaño más grande, con las barras del navegador escondidas, y al cargar la página las barras están visibles. Para eso se crearon dvh (que cambia cuando aparecen y desaparecen las barras), svh (la ventana con las barras visibles) y lvh (sin ellas). Usa min-height: 100dvh para una sección que debe llenar la pantalla. Esta no se puede comprobar en un navegador de escritorio, porque no tiene barras que aparezcan y desaparezcan.
Cada rareza se comprobó en Chromium, Firefox y WebKit (los motores de Chrome, Firefox y Safari) con Playwright, midiendo tamaños, posiciones y colores; las cifras del texto salen de esas medidas. El HTML pasó por el Nu Html Checker 26.11.4. ¿Quieres probar una variante? 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.
