Qué se hereda, qué no, y con qué criterio se decidió
La lógica detrás de la lista de propiedades heredables, las excepciones que hay que conocer y la diferencia entre heredar un valor y afectar a los descendientes.
La herencia parece la parte fácil de CSS hasta que te preguntas por qué color se hereda y background-color no, por qué text-shadow sí y box-shadow no, o por qué opacity afecta a todos los descendientes sin heredarse en absoluto. Detrás de la lista hay un criterio bastante coherente, y conocerlo permite predecir el comportamiento de una propiedad que no has visto nunca en lugar de tener que consultarla.
- Enunciar el criterio que separa las propiedades heredables de las que no lo son.
- Predecir si una propiedad desconocida se hereda a partir de lo que describe.
- Distinguir heredar un valor de producir un efecto sobre los descendientes.
- Explicar por qué la herencia transmite el valor calculado y qué consecuencia tiene.
El criterio
La definición de cada propiedad incluye una línea que dice si es heredable, y esa decisión se tomó propiedad a propiedad, pero siguiendo un principio bastante estable: se hereda lo que describe el entorno del contenido; no se hereda lo que describe la caja.
Piénsalo desde el lado del absurdo, que es como se decidió. Si margin se heredara, poner un margen de dos rem en un artículo se lo pondría también a cada párrafo, a cada enlace y a cada palabra en negrita de dentro. Si border se heredara, un recuadro alrededor de una tarjeta produciría un recuadro alrededor de cada elemento interior. Ninguna de esas cosas es lo que nadie quiere jamás.
Al revés ocurre lo mismo. Si color no se heredara, tendrías que declararlo en cada elemento que contenga texto, incluidos los que genera un editor de contenidos y que no puedes anticipar. Si font-family no se heredara, cambiar la tipografía de un sitio sería imposible sin enumerar cada etiqueta.
La regla mnemotécnica que funciona: si la propiedad tuviera sentido dicha sobre un párrafo entero y sobre cada palabra dentro de él por igual, se hereda. “Este texto es rojo” tiene sentido a cualquier escala. “Este texto tiene veinte píxeles de margen” solo tiene sentido a la escala de la caja que lo contiene.
| Se heredan | No se heredan |
|---|---|
color, font-family, font-size, font-weight |
background, border, padding, margin |
line-height, letter-spacing, word-spacing |
display, position, inset |
text-align, text-indent, text-transform |
inline-size, block-size, overflow |
text-shadow |
box-shadow, filter, opacity |
white-space, overflow-wrap, hyphens, tab-size |
z-index, transform |
visibility, cursor, direction, writing-mode |
Todas las de Flexbox y Grid |
list-style, quotes |
gap, place-items |
border-collapse, border-spacing, caption-side |
contain, content-visibility |
| Las custom properties |
La última fila es la que más consecuencias tiene en CSS moderno: toda custom property se hereda por defecto. Eso es lo que convierte a las variables en el mecanismo natural de tematización por contexto, y también lo que obliga a nombrarlas con cuidado, porque una variable definida arriba llega a todo lo de abajo.
Dos filas merecen comentario. text-shadow se hereda y box-shadow no, coherentemente con el criterio: la primera describe cómo se dibuja el texto, la segunda cómo se dibuja la caja. Y las propiedades de tablas —border-collapse, border-spacing, caption-side— se heredan porque tienen que atravesar la estructura anónima que el motor construye entre la tabla y sus celdas, donde no hay elementos a los que apuntar con un selector.
opacity es el ejemplo perfecto y merece desmenuzarse. No es heredable: el valor calculado de opacity en un hijo es 1 aunque el padre tenga 0.5. Y sin embargo todo el subárbol se ve translúcido. La explicación es que opacity no transmite un valor, produce un efecto de composición: cuando vale menos de uno, el elemento y todo su contenido se pintan primero en una superficie aparte y esa superficie se compone después con el fondo aplicando la transparencia. Los descendientes no reciben ninguna opacidad; lo que reciben es el destino de acabar dentro de un grupo que se compone al final. La consecuencia práctica es contundente y aparece constantemente: un hijo no puede recuperar la opacidad total poniéndose opacity: 1, porque la transparencia no está en él sino en cómo se compone su ancestro. La misma lógica se aplica a filter, a mask y a transform: ninguna se hereda, todas afectan al subárbol entero, y ninguna se puede deshacer desde dentro. Compáralo con visibility, que sí es heredable y por tanto sí se puede revertir: dentro de un ancestro con visibility: hidden, un descendiente con visibility: visible vuelve a verse, porque lo que recibió fue un valor y el valor se puede sobrescribir. Y compáralo con display: none, que no se hereda y aun así no admite rescate, porque el elemento no genera caja y sus descendientes tampoco tienen dónde existir. Tres comportamientos que parecen el mismo desde fuera y que responden a tres mecanismos distintos: valor heredado, efecto de composición y ausencia de caja. Cuando algo “no se puede deshacer desde dentro”, la pregunta correcta es a cuál de los tres pertenece.
Se hereda el valor calculado, no el declarado
Un detalle preciso con consecuencias muy visibles: lo que pasa de padre a hijo es el valor calculado de la propiedad en el padre, no lo que el padre tiene escrito. Ya se ha absolutizado lo que se podía absolutizar.
De ahí sale el comportamiento acumulativo de las unidades relativas:
.contenedor { font-size: 20px; }
.contenedor ul { font-size: 0.8em; } /* 16px */
.contenedor ul ul { font-size: 0.8em; } /* 12.8px, no 16px */
Cada nivel resuelve su em contra el tamaño ya calculado del padre, y en una lista anidada el texto se encoge hasta desaparecer. Es la razón por la que las escalas tipográficas serias se construyen con rem, que siempre se resuelve contra la raíz, y se reserva em para lo que debe escalar con su contexto, como el espaciado interno de un botón.
line-height es la excepción interesante y conviene conocerla. Si le das un número sin unidad, lo que se hereda es el número, y cada descendiente lo multiplica por su propio tamaño de fuente. Si le das una longitud o un porcentaje, lo que se hereda es el resultado ya calculado, y un titular grande heredará el interlineado del texto normal, quedándose con las líneas superpuestas.
/* Correcto: cada elemento calcula el suyo */
body { line-height: 1.5; }
/* Problemático: todos heredan la misma altura en píxeles */
body { line-height: 150%; }
Dónde viaja la herencia
Tres precisiones sobre el camino que recorre.
Va por el árbol aplanado, no por el árbol del documento. Cuando hay componentes con árboles de sombra, un elemento colocado en una ranura hereda del sitio donde se le ha colocado, no de donde está escrito en el marcado. Esa es la razón de que las propiedades heredables y las custom properties atraviesen sin problema la frontera de encapsulación que los selectores no pueden cruzar.
No la interrumpen las cajas anónimas. El motor crea cajas intermedias en varias situaciones —dentro de tablas, alrededor de texto suelto en un contenedor de bloques— y esas cajas no cortan la cadena: la herencia sigue el árbol de elementos, no el de cajas.
Los pseudo-elementos heredan de su elemento originario. Un ::before hereda el color y la tipografía del elemento en el que se genera, lo que permite que el contenido generado se integre sin declarar nada.
Y para cerrar, el caso de la raíz: el elemento raíz no tiene padre del que heredar, así que las propiedades heredables toman ahí su valor inicial. Ese valor inicial no es necesariamente lo que ves, porque encima actúa la hoja del navegador; la diferencia entre esas dos cosas es exactamente lo que separa initial de revert, que es el asunto de la lección siguiente.