Extends in less

For a long time now, extends in less have been bubbling away. When I joined the less team in September last year it was one of the highest asked for feature requests. We decided that 1.3.x releases would fix bugs and implement minor feature requests and that 1.4.0 would include extends.

Well, I have finished it and the beta has been released! You can get it from npm with "npm install less@beta".

History

Extends were implemented by sass first - here is an initial blog post on them and the current sass documentation. Both of these links explain extend as the reverse of mixins. e.g. if I have the current (badly written, repeating) CSS

.button {
background: white;
display: block;
width: 120px;
}
.blue-button {
display: block;
width: 120px;
background: blue;
}

then I can use mixins to "abstract" out some of the properties (see below)

.button {
background: white;
display: block;
width: 120px;
}
.blue-button {
.button();
background: blue;
}

However, the output is still the same. With extends I can say that one class wants to have the properties of another by the inclusion of the selector in the first selector block. e.g. I write the following code (modified to be in the less format)..

.button {
background: white;
display: block;
width: 120px;
}
.blue-button {
&:extend(.button);
background: blue;
}

and I get this output

.button, .blue-button {
background: white;
display: block;
width: 120px;
}
.blue-button {
background: blue;
}

Notice how the extend has copied the selector it is on, into the selector for .button.

After the sass release this has then been used for a number of different purposes1. As above, in order to get inheritance on styles (an alternative would be putting all classes on the dom element and relying on the ordering in the css to determine the hierarchy)2. As a way of replacing one class with another, if you cannot alter your HTML or if for instance you have generated a grid file and want to assign more meaningful class names.3. variations on the above - for instance if you have a clearfix hack which is applied to alot of elements, you can use extend to use the clear fix styles but not duplicate all the properties (you end up with a long list of selectors but the properties are not duplicated and it neatly tidies away that you are using this hack

However, Sass extend has a limitation.. the target can only extend a single class name or element. This means that they have to do some extra jiggling to make sure one of the selectors match. e.g. from their documentation, the following

#admin .tabbar a {
font-weight: bold;
}
#demo .overview .fakelink {
@extend a;
}

becomes

#admin .tabbar a,
#admin .tabbar #demo .overview .fakelink,
#demo .overview #admin .tabbar .fakelink {
font-weight: bold;
}

The other limitation is that when you extend a class, you extend it everywhere.. there is no way to extend only the class you are referencing.

Whilst this approach works well for some selectors, for others it generates a lot of bloat.

Less Implementation

First off, extend works on selectors, so we decided that you should be able to specify an extend inside a selector, so we allow you to specify an extend like this

.a {
color: black;
}
.b:extend(.a) {
background: white;
}

and in a statement form

.a {
color: black;
}
.b {
&:extend(.a);
background: white;
}

In the statement form it applies to every one of the comma separated selectors and in the selector form it is applied to each individual selector, as you would expect of a pseudo selector.

By default it matches only the selector you put in and gives you easily predictable results. e.g.

.button {
color: black;
&:hover {
color: white;
background: black;
}
}
.button-red {
&:extend(.button);
border-color: red;
}

will not touch the &:hover rule and give this output...

.button,
.button-red {
color: black;
}
.button:hover {
color: white;
background: black;
}
.button-red {
color: red;
}

but in this case, we want the hover state, so we specify all on the extend to match every selector that matches

.button {
color: black;
&:hover {
color: white;
background: black;
}
}
.button-red {
&:extend(.button all);
border-color: red;
}

and we get

.button,
.button-red {
color: black;
}
.button:hover,
.button-red:hover {
color: white;
background: black;
}
.button-red {
color: red;
}

Furthermore, you can specify any selector within your extend.. this example calls a mixin that then transforms the :nth-child selector into the .first selector.

tr:nth-child(1) {
color: black;
}

.add_ie_support() {
.first:extend(:nth-child(1) all) {
}
}

.add_ie_support();

outputting..

tr:nth-child(1),
tr.first {
color: black;
}

The potential for writing less that outputs better CSS is greatly improved and there is particular relevance for people who use libraries, to essentially re-write the selectors, e.g. I can import a library (like bootstrap) and then convert any selector containing ".navbar .menu" into my own single class.

.mymenu:extend(.navbar .menu all) {}

This will be even more useful when we release silent imports, allowing you to pull out a section of selectors and not include the rest of the library.. but that will be 1.4.1.

I hope people find it useful!

MORE BY LUKE

Seven Surprising JavaScript 'Features'

Aurelia, less2css and bundling

blog comments powered by Disqus