La herencia como herramienta de arquitectura
Diseñar componentes que leen su contexto en lugar de declararlo, el papel de currentColor, el coste real de cambiar una propiedad heredable y qué prácticas destruyen la herencia.
La herencia se enseña como un comportamiento del que hay que defenderse y es exactamente lo contrario: es el único mecanismo de CSS que propaga contexto sin acoplamiento, y un sistema de componentes diseñado alrededor de ella necesita muchísimo menos código que uno que la ignora. Esta lección cierra el nivel con las prácticas que la aprovechan y con las que la destruyen, que resultan estar entre las más repetidas del oficio.
- Diseñar un componente que herede su tipografía y su color en lugar de declararlos.
- Usar
currentColorcomo canal de propagación de un único valor a varias propiedades. - Valorar el coste de recálculo de cambiar una propiedad heredable cerca de la raíz.
- Identificar las declaraciones que impiden que la herencia siga funcionando.
Declarar arriba y dejar que baje
El patrón más rentable de todo este nivel es también el más simple: declarar el entorno una vez, arriba, y no volver a declararlo.
:root {
font-family: system-ui, sans-serif;
line-height: 1.5;
color: oklch(25% 0.02 260);
color-scheme: light dark;
}
A partir de ahí, ningún componente declara tipografía ni color de texto salvo que tenga una razón concreta. Un componente que no declara color funciona igual dentro de una zona clara y de una zona oscura, porque toma el que le llegue. Uno que sí lo declara necesita una variante por cada contexto en el que pueda aparecer, y esa combinatoria es la que hace crecer los sistemas de diseño hasta volverse inmanejables.
La regla operativa que se deduce: declarar una propiedad heredable en un componente es asumir un compromiso sobre todos los contextos en los que ese componente pueda aparecer. A veces es lo correcto, pero conviene hacerlo a sabiendas y no por costumbre.
La excepción conocida son los controles de formulario, que por razones históricas no heredan la tipografía. Una línea en el reset lo arregla para todo el proyecto:
input, button, textarea, select { font: inherit; }
currentColor, un canal heredado de propósito general
currentColor es el valor calculado de la propiedad color en ese mismo elemento, y se puede usar como valor de casi cualquier propiedad que acepte un color. Como color se hereda, eso convierte a una única propiedad heredable en un canal que alimenta a todas las demás:
.icono {
fill: currentColor;
stroke: currentColor;
}
.chip {
border: 1px solid currentColor;
box-shadow: 0 0 0 3px color-mix(in oklch, currentColor 15%, transparent);
}
.enlace:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
Con eso, cambiar un solo color en cualquier ancestro reajusta iconos, bordes, sombras y contornos de todo el subárbol, sin variables, sin clases de variante y sin que ningún componente sepa nada del tema. Es la forma más económica de tematizar que existe en CSS, y funciona incluso dentro de árboles de sombra, porque lo que viaja es una propiedad heredable.
Para sistemas que necesitan más de un canal, las custom properties hacen lo mismo con tantos valores como quieras, porque también se heredan:
.zona-oscura {
--superficie: oklch(22% 0.02 260);
--texto: oklch(95% 0.01 260);
background: var(--superficie);
color: var(--texto);
}
Cualquier componente dentro de .zona-oscura que lea var(--superficie) se adapta sin saber dónde está. Fíjate en que la herencia está haciendo el trabajo de un ámbito: propaga contexto hacia abajo, se detiene donde alguien la redefine, y no exige que ningún selector conozca la estructura.
Aquí está la práctica más extendida que trabaja en contra de todo lo anterior. Cuando escribes una declaración con el selector universal, estás creando una declaración explícita en cada elemento del documento, y una declaración explícita gana siempre a un valor heredado. Para propiedades que no se heredan eso es inofensivo y a veces necesario: *, *::before, *::after { box-sizing: border-box; } es correcto precisamente porque box-sizing no es heredable y no hay herencia que romper. Pero aplicado a una propiedad heredable, el efecto es demoledor y permanente: * { font-family: system-ui; } hace que ningún contenedor pueda volver a cambiar la tipografía de su subárbol, porque cada descendiente tiene su propia declaración ganando a lo que le llegue de arriba. El síntoma aparece meses después, cuando alguien pone font-family: monospace en un bloque de código y no pasa nada, y nadie relaciona el fallo con una línea del reset escrita el primer día. Lo mismo vale para * { color: inherit }, que suena inofensivo y es peor: convierte la herencia natural, que solo actuaba en ausencia de reglas, en una declaración explícita que gana a cualquier valor que otro componente intente aportar. La regla es fácil de recordar y no tiene excepciones útiles: el selector universal solo para propiedades no heredables. Si necesitas un valor por defecto para una propiedad heredable, decláralo en :root o en body y deja que baje solo, que es lo que la herencia hace mejor. Y si de verdad necesitas que una propiedad no heredable se propague hacia abajo pero siga siendo sobrescribible por subárboles, existe un patrón exacto para eso: declárala en la raíz y usa inherit en el universal, que reintroduce la posibilidad de que un contenedor intermedio cambie el valor para todo lo que tenga debajo.
html { box-sizing: border-box; }
*, *::before, *::after { box-sizing: inherit; }
/* Ahora esto sí funciona para todo el subarbol */
.widget-heredado { box-sizing: content-box; }El coste de cambiar algo heredable
Hay una consecuencia de rendimiento que conviene conocer antes de diseñar un cambio de tema. Cuando cambia el valor de una propiedad heredable en un elemento, el motor tiene que recalcular el estilo de todo su subárbol, porque cualquier descendiente podría estar tomando ese valor. Cambiar algo heredable en la raíz es, por tanto, invalidar el documento entero.
Eso no significa que haya que evitarlo: significa que hay que saber cuándo se paga. Un cambio de tema por acción del usuario ocurre una vez y un recálculo completo de estilo en una página normal se mide en pocos milisegundos, así que es perfectamente asumible. Lo que no es asumible es hacerlo en cada fotograma: animar una custom property heredable declarada en :root obliga a un recálculo de todo el documento sesenta veces por segundo, y eso sí aparece en el perfil como una barra continua.
La medida práctica es acotar el punto de declaración: si una variable solo la usa un componente, decláralas en ese componente y no en la raíz. El alcance de la invalidación es el subárbol del elemento donde declaras, así que declarar bajo es declarar barato.
Aislar cuando hace falta
La otra cara: a veces quieres que la herencia no llegue. El caso típico es una isla de contenido ajeno —el cuerpo de un correo, la salida de un editor de terceros, un fragmento renderizado por un sistema que no controlas— donde tus estilos heredables producen resultados imprevisibles.
.isla-ajena {
all: revert;
}
all: revert deja ese elemento como lo mostraría el navegador sin tu CSS de autor, incluidas las propiedades heredables, que dejan de tomar tus valores. Es menos brutal que all: initial, que produciría un elemento que no se parece a nada, y suele ser lo que se pretendía.
Ten en cuenta que esto afecta al elemento, no a su subárbol: los descendientes heredarán de él, que ahora tiene valores de navegador, y eso es justo lo que buscabas.
- Busca en tu proyecto todas las reglas que declaran
colorofont-familyen componentes y cuenta cuántas hay. - Elimina las que solo repiten lo que ya llega por herencia y comprueba que nada cambia.
- Sustituye los colores fijos de bordes e iconos por
currentColory verifica que un cambio decoloren un contenedor los arrastra a todos. - Busca declaraciones de propiedades heredables con el selector universal y comprueba qué subárbol deja de poder sobrescribirlas.