CSS Nesting: CSS anidado

Aunque actualmente no está soportado por los navegadores, existe una propuesta de anidamiento CSS para dar soporte a una característica que proporcionaría una mejor legibilidad al CSS nativo, por lo que en el futuro es muy probable que esté soportada y se pueda utilizar directamente.

La idea detrás del concepto CSS Nesting es la posibilidad de crear reglas CSS (bloques de código CSS) dentro de otras reglas CSS, anidando código y haciéndolo mucho más fácil de entender y mantener.

CSS Nesting

No obstante, si estamos utilizando PostCSS en nuestro proyecto, podemos utilizarla ya mismo traduciéndola a CSS nativo con esta herramienta, sin tener que esperar a que los navegadores la soporten, con alguno de los siguientes plugins:

Plugin Autor Tipo de animamiento
postcss-nesting jonathanneal Anidamiento CSS estándar anterior de CSS Nesting
@csstools/postcss-nesting-experimental csstools Anidamiento CSS estándar actual de CSS Nesting
postcss-to-nest jonathanneal Transforma CSS sin anidar en CSS anidado.
postcss-nested postcss Anidamiento CSS con sintaxis de Sass.

Veremos más adelante como instalarlo y utilizarlo, pero vamos a explicar primero en que consiste el CSS Nesting (anidamiento CSS).

Anidamiento CSS

Cuando escribimos CSS, tenemos que dominar y utilizar selectores CSS básicos y selectores CSS avanzados para seleccionar los elementos a los que queremos dar estilo y escribir nuestras reglas específicas. Con el CSS Nesting (anidamiento de CSS) no es que evitemos utilizarlos, sino que los utilizaremos menos porque usando el indentado (similar a como ocurre en Python) estaremos creando selectores, sólo que de una forma «más lógica para humanos».

El CSS Nesting se basa en la posibilidad de incluir bloques de CSS uno dentro de otro (algo que no es posible actualmente en CSS nativo), de modo que facilita la organización del código a medida que se lee. Se utilizará el carácter & para indicar que se sustituye por todo el selector padre que tengamos (en este ejemplo, .item, pero en casos con mayor anidamiento será más largo):

.item {
padding: 10px;

& .warning {
background: red;
color: white;
}
}

Desde los cambios del pasado 28 de Octubre de 2022, el anidado permite omitir el carácter &, algo que antiguamente no era posible. De esta forma, tenemos una sintaxis mucho menos estricta y es posible utilizar combinadores de CSS más avanzados de forma más legible.

Tenemos la clase .warning en el interior del bloque .item, por lo que eso implica que sólo se le dará estilo CSS a las clases .warning que estén dentro del elemento .item. Esto se traduce a CSS nativo de la siguiente forma:

.item {
padding: 10px;
}

.item .warning {
background: red;
color: white;
}

Quizás con este ejemplo aún no se vea claramente la ventaja del anidamiento CSS, pero a medida que escribimos más código las ventajas se hacen evidentes. Si has trabajado con CSS durante algún tiempo, habrás comprobado que una de las cosas más complejas de CSS es mantener el código a medida que crece. En este apartado es donde brilla el anidamiento.

Grandes ventajas de utilizar CSS Nesting:

  • El primer nivel de anidamiento se puede usar como un «componente» o entidad.
  • Simplifica mucho los selectores CSS, haciéndolos más intuitivos (sobre todo para novatos).
  • Al indentar, el código se hace mucho más legible.
  • Al agrupar con comas y anidar conseguimos mucha más flexibilidad en menos código.
  • Buscar fragmentos de código es mucho más fácil (si somos organizados).

Compliquemos un poco más un código de ejemplo con anidamiento CSS:

.menu,
.sidebar
{
background: black;
color: white;
padding: 10px;

& a {
color: #333399;
font-size: 1.25rem;
}

& .warning {
background: red;
color: white;
}
}

.warning {
color: red;
}

Observa que en este ejemplo tenemos los elementos a y las clases .warning tanto dentro de clases .menu como de clases .sidebar. Esto nos permitirá sustancialmente evitar repetir código. Este ejemplo se traduciría a CSS nativo como veremos a continuación:

.menu,
.sidebar
{
background: black;
color: white;
padding: 10px;
}

.menu a,
.sidebar a
{
color: #333399;
font-size: 1.25rem;
}

.menu .warning,
.sidebar .warning
{
background: red;
color: white;
}

.warning {
color: red;
}

Como ves, es mucho más fácil de leer el ejemplo superior con anidamiento CSS que este último, donde a medida que crece es mucho menos legible.

Anidamiento sobre el padre

Un detalle interesante a tener en cuenta es que podemos anidar selectores sobre el padre, simplemente teniendo en cuenta si existe o no existe espacio entre el símbolo & de anidamiento.

.item {
background: grey;

&:hover {
background: red;
}
}

En este fragmento de código, el selector anidado &:hover realmente está haciendo referencia al selector .item:hover, es decir, cuando tenemos el ratón sobre el elemento .item.

Pero por otro lado, si añadieramos un espacio en el selector anidado & :hover estaríamos haciendo referencia a .item :hover, que tiene un matiz diferente al anterior: seleccionamos cuando tenemos el ratón sobre un elemento que está dentro de .item.

Anidamiento en ancestros

En algunos casos, es posible que se quiera hacer referencia a un elemento dependiendo de los ancestros (padres, abuelos, etc...) que tenga un cierto elemento. Esto permitiría flexibilizar la forma de anidar selectores en nuestro código y hacerlo mucho más potente aún.

Por ejemplo, podemos utilizar el siguiente código para hacer referencia a cualquier mención del padre de primer nivel:

.item {
background: grey;

.container & {
background: green;
}
}

Esto nos permitirá indicar al navegador que queremos dar estilo al elemento .item siempre y cuando tengan un ancestro con clase .container. Así podremos organizar grupos de código CSS donde aparezca cualquier mención a un determinado elemento.

El código equivalente en CSS nativo sería el siguiente:

.item {
background: grey;
}

.container .item {
background: green;
}

Como vemos, se ha reemplazado el & por el selector que está anidando. Ten en cuenta que esta sintaxis es uno de los cambios de la última versión de la especificación del CSS Nesting.

Instalación del plugin de PostCSS

En principio, las opciones que recomiendo son las siguientes:

El primer paso sería instalar el paquete como una dependencia de desarrollo. Recuerda instalar el paquete postcss-cli si no tienes PostCSS en tu proyecto, aunque lo más común hoy en día es utilizar un empaquetador como Vite:

$ npm install --save-dev postcss-cli @csstools/postcss-nesting-experimental

Una vez instalado, abrimos el fichero de configuración postcss.config.js en la carpeta raíz de nuestro proyecto. En él añadiremos @csstools/postcss-nesting-experimental (o el plugin deseado) a la lista de plugins que estemos usando con postcss:

module.exports = {
"plugins": {
"@csstools/postcss-nesting-experimental": true,
"autoprefixer": true
}
}

En este caso, observa que tenemos autoprefixer instalado también (aunque no es obligatorio para este ejemplo) y lo hemos colocado después de nuestro plugin de nesting. El orden importa, ya que es el orden con el cuál PostCSS procesará los plugins y los aplicará.

Con esto tendríamos el plugin de PostCSS instalado. Ahora vamos a crear un fichero index.css para hacer un ejemplo y observar que funciona correctamente:

.item {
background: grey;

& .warning {
background: red;
animation: jump 10s linear;
}

.container & {
border: 2px solid black;
}
}

Una vez guardado en src/css/index.css, podemos probar a ejecutar postcss y observa si aplica los cambios de los dos plugins instalados:

$ npx postcss src/css/index.css --no-map

Si todo está configurado correctamente, PostCSS debería sacar este código CSS, donde han sido aplicados los plugins:

.item {
background: grey;
}
.item .warning {
background: red;
-webkit-animation: jump 10s linear;
animation: jump 10s linear;
}
.container .item {
border: 2px solid black;
}

El prefijo -webkit-animation es añadido por el plugin autoprefixer, que no tiene nada que ver con este capítulo. Si te interesa, puedes aprender sobre él en Plugins de PostCSS: Autoprefixer.

Ten en cuenta que el plugin postcss-nesting forma parte de un pack de plugins más grande llamado postcss-preset-env, que se encarga de transpilar nuestro código CSS a una versión más antigua y con más soporte, de forma similar a como lo hace Babel en Javascript, por lo que quizás te interese dicha aproximación.

Tabla de contenidos