CSS Nesting: CSS anidado

Convierte CSS nesting en CSS sin nesting


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.

OJO: Actualmente, CSS ya soporta CSS Nesting nativo, por lo que no hace falta herramientas como esta. Sin embargo, te puede interesar si quieres escribir código CSS con nesting y que genere código CSS equivalente sin nesting, permitiendo mayor compatibilidad con navegadores antiguos.

CSS Nesting

Como hemos dicho, las versiones actuales de CSS ya soportan CSS Nesting, pero aún existen casos de uso donde podemos querer utilizar nesting en nuestro código CSS, pero forzar a que convierta en un CSS equivalente sin anidado. En ese caso, PostCSS es una excelente opción.

Para ello, debemos utilizar alguno de los siguientes plugins:

PluginAutorTipo de animamiento
postcss-nestingcsstoolsAnidamiento CSS compatible con el estándar de CSS Nesting
postcss-to-nestjonathannealTransforma CSS sin anidar en CSS anidado.
postcss-nestedpostcssAnidamiento 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 el pasado 28 de Octubre de 2022, el anidado CSS nativo permite omitir el carácter &, algo que no era posible hasta ese momento. Esta sintaxis es mucho menos estricta y es posible utilizar combinadores de CSS más avanzados de forma más legible. Más en CSS Nesting nativo.

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:

$ npm install -D @csstools/postcss-nesting

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

export default {
  "plugins": {
    "postcss-nesting": 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, podemos comprobar el CSS generado. Si todo está configurado correctamente, PostCSS debería sacar un código CSS donde han sido aplicados los plugins. Algo similar a esto:

.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.

¿Quién soy yo?

Soy Manz, vivo en Tenerife (España) y soy streamer partner en Twitch y profesor. Me apasiona el universo de la programación web, el diseño y desarrollo web y la tecnología en general. Aunque soy full-stack, mi pasión es el front-end, la terminal y crear cosas divertidas y locas.

Puedes encontrar más sobre mi en Manz.dev