ProgramandooIntentándolo

Tutorial Less: 5. Herencia en Less (Extend)

Logo less

Con Less se introduce el concepto de herencia, otro de los puntos que se echaba de menos en CSS y que nos va a ayudar a estructurar mejor nuestros estilos además de permitirnos eliminar el uso de clases base en nuestro HTML.

¿Cómo se utiliza la herencia en Less?

Para hacer que un selector en Less herede las propiedades de otro hay que utilizar la pseudo-clase :extend() que se puede usar de 2 modos. Como hacemos habitualmente con las pseudo-clases o bien usando el operador & como vimos en el primer artículo del tutorial, el resultado es el mismo de ambas formas así que usar una u otra forma es cuestión de gustos, personalmente me quedo con la primera.

Es importante que extend siempre sea la última pseudo-clase porque si no se producirá un fallo en la compilación.


.boton {
  width: 100px;
  height: 20px;
}

.boton-error:extend(.boton) {
  background: #a22;
  color: #000;
}

.boton-ok {
  &:extend(.boton);

  background: #040;
  color: #fff;
}

Y el CSS que se genera como resultado es que se aplican las propiedades de la clase boton a boton-error y boton-ok, nada que no pudiésemos hacer solo con CSS y sin mucho trabajo.


.boton,
.boton-error,
.boton-ok {
  width: 100px;
  height: 20px;
}
.boton-error {
  background: #a22;
  color: #000;
}
.boton-ok {
  background: #040;
  color: #fff;
}

Pero mientras con la herencia solo tenemos los atributos de .boton-error por ejemplo en un lugar y decimos que hereda también las propiedades de .boton en el CSS tenemos los atributos repartidos en 2 sitios y en este caso como es un ejemplo pues están seguidos por lo que la visibilidad no se ve afectada significativamente, pero y si hereda de unas cuantas clases o si la definición de .boton y .boton-error están separadas por 1000 líneas o peor, si están en diferentes archivos entonces ya se ve que clarifican mucho el código.

Puede que aun viendo lo anterior sigas pensando que no aporta nada porque no escribes los estilos de esa forma, si no que prefieres aplicarlos directamente así <div class="boton boton-ok">Aceptar</div> con lo que el CSS sería este.


.boton {
  width: 100px;
  height: 20px;
}

.boton-error {
  background: #a22;
  color: #000;
}

.boton-ok {
  background: #040;
  color: #fff;
}

¿Y si decides que .boton-error en lugar de extender la clase .boton extiende .superBoton? Pues tienes que ir cambiando por todo tu html las referencias cuando en realidad los boton-ok y boton-error siguen existiendo y con la herencia lo podrías haber solucionado cambiando solo la clase de la que extendían ambas por que tu html seria <div class="boton-ok">Aceptar</div>.

Herencia múltiple

Un selector puede heredar sus atributos de más de varios selectores al mismo tiempo para lo que podemos añadir un nuevo extend por cada selector al que se quiere extender o bien un utilizar la forma compacta de extend en la que se pueden indicar varios selectores separados por comas.


.padre {
  width: 40cm;
  height: 180cm;
}

.madre {
  color: green;
}

.hijoMarciano:extend(.padre):extend(.madre) {
  background: blue; 
}

.hijaMarciana:extend(.padre, .madre) {
  opacity: 0.5;
}

Y el resultado es este.


.padre,
.hijoMarciano,
.hijaMarciana {
  width: 40cm;
  height: 180cm;
}
.madre,
.hijoMarciano,
.hijaMarciana {
  color: green;
}
.hijoMarciano {
  background: blue;
}
.hijaMarciana {
  opacity: 0.5;
}

Extends con selectores anidados

En los anteriores ejemplos hemos extendido las propiedades de un clase pero el selector del que se extiende puede ser compuesto como en el siguiente ejemplo.


#contenedor {
  background: #ccc;
}

#contenedor .cajon p {
  width: 100px;
}

#contenedor .cajon p.aviso {
  width: 100px;
  color: red;
}

#cosa:extend(#contenedor .cajon p) {
  height: 20px;
}

Cuando se usa el extend este se hace solo de los elementos que coinciden exactamente, por eso #cosa solo hereda las propiedades de contenedor .cajon p y no lo hace con selectores más específicos ni más genéricos.


#contenedor {
  background: #ccc;
}
#contenedor .cajon p,
#cosa {
  width: 100px;
}
#contenedor .cajon p.aviso {
  width: 100px;
  color: red;
}
#cosa {
  height: 20px;
} 

Herencia multinivel (Extend de extend)

En Less si un selector hereda las propiedades de un selector que a su vez también hereda las propiedades de otro, hereda tanto las propiedades del primero como las del segundo, es decir, la herencia se va aplicando desde el nivel superior al inferior con lo que el último elemento hereda las propiedades de la jerarquía entera de extends.


.clase1 {
  color: red;
}

.clase2:extend(.clase1) {
  background: green;
}

.clase3:extend(.clase2) {
  height: 10px;
} 

Con la explicación puede que no quede claro al 100% pero con un ejemplo es muy sencillo.


.clase1,
.clase2,
.clase3 {
  color: red;
}
.clase2,
.clase3 {
  background: green;
}
.clase3 {
  height: 10px;
}

Extend “all”

En el ejemplo anterior vimos que el extend solo actúa sobre las coincidencias exactas pero se puede forzar para que extienda cualquier selector que contenga el valor que le indiquemos si le añadimos all, veamos un ejemplo.


.enlace {
  background: #ccc;
  
  &:hover {
    border: 2px solid #000;
  }
}

#contenedor .enlace {
  font-size: 10px;
}

.enlace2:extend(.enlace all) {
  height: 20px;
}

Como hemos indicado en el extend que queremos extender la clase .enlace con all se le aplicarán a la clase .enlace2 todas las propiedades de todos los selectores que tuviesen .enlace en algún sitio.


.enlace,
.enlace2 {
  background: #ccc;
}
.enlace:hover,
.enlace2:hover {
  border: 2px solid #000;
}
#contenedor .enlace,
#contenedor .enlace2 {
  font-size: 10px;
}
.enlace2 {
  height: 20px;
}

Como podemos ver en el CSS generado el resultado es que tenemos aplicadas todas las estructuras de estilos en las que aparecía .enlace a .enlace2, es decir, el extend all sirve para copiar una jerarquía de estilos completa.

Extend en @media queries

A la hora de aplicar la herencia hay que tener presente que si el extend se hace dentro de una media query este solo tiene efecto en su interior, por lo que no se extenderá el selector ni fuera ni tampoco en otra media query distinta.

En cambio si el extend se hace fuera si que aplica a todas las media queries, vamos a ver un par de ejemplos.


.botoncito {
  width: 100px;
}

.superBoton:extend(.botoncito) {
  width: 110px;
  background: #040;
  border: 10px solid #020;
}

.botonazo {
  width: 400px;
  height: 200px;
}

// No extiende a .botonazo porque la herencia esta en otro ambito 
.superBotonazo {
  width: 600px;
}

@media (min-width: 1024px) {
  .botoncito {  
    width: 80px;
  }

  // No hace nada porque .botonazo no existe dentro de esta media query
  .superBotonazo:extend(.botonazo) {
    width: 300px;
  }
}

Aquí tenemos el ejemplo del extend exterior de .botoncito declarado sobre .superBoton que se aplica tanto fuera como dentro porque hacia abajo es visible y del interior con .superBotonazo que extiende .botonazo pero que no tiene ningún resultado porque por un lado no existe la clase .botonazo dentro de la media query y por el otro fuera aunque sí que existe como no es visible el extend tampoco tiene ningún efecto.


.botoncito,
.superBoton {
  width: 100px;
}
.superBoton {
  width: 110px;
  background: #040;
  border: 10px solid #020;
}
.botonazo {
  width: 400px;
  height: 200px;
}
.superBotonazo {
  width: 600px;
}
@media (min-width: 1024px) {
  .botoncito,
  .superBoton {
    width: 80px;
  }
  .superBotonazo {
    width: 300px;
  }
} 

Es decir, no se puede utilizar el extend dentro de una media query para heredar atributos de una clase que este en un nivel superior.

Y otro ejemplo más para ver otro de los matices, en este caso .miniBoton extendiente a .superBoton y como en este caso .superBoton sí que existe dentro si hereda sus propiedades, pero solo las suyas porque aunque .superBoton también hereda de .botoncito y se podría esperar que tuviese también las propiedades de esta última clase esto no sucede porque el extend dentro de una media query no sale hacia afuera con lo que en ese sentido no es visible y por tanto no se aplica.


.botoncito {
  width: 100px;
}

.superBoton:extend(.botoncito) {
  width: 110px;
  background: #040;
  border: 10px solid #020;
}

@media (min-width: 768px) {
  .superBoton {
    color: #400;
  }

  .botoncito {  
    width: 60px;
  }

  // Extiende .superBoton pero no .botoncito
  .miniBoton:extend(.superBoton) {
    height: 5px;
  }
}

Eso si hacia abajo o hacia dentro sí que se aplica como habíamos visto antes.


.botoncito,
.superBoton {
  width: 100px;
}
.superBoton {
  width: 110px;
  background: #040;
  border: 10px solid #020;
} 
@media (min-width: 768px) {
  .superBoton,
  .miniBoton {
    color: #400;
  }
  .botoncito,
  .superBoton {
    width: 60px;
  }
  .miniBoton {
    height: 5px;
  }
} 

En este enlace puedes descargar un archivo .less con todos los ejemplos.