-
Notifications
You must be signed in to change notification settings - Fork 47
Less Language Extends
If you have two selectors and want them to have exactly the same declarations in exactly the same order on exactly the same css locations, then you have to makes sure that every ruleset with one of them contains also the other one. Less extend keyword allows you to achieve this without having to manually copy any of these two selectors.
Extend allows you to copy selector into every ruleset that contains some other selector. For example, next less sample copies pre:hover
selector into every ruleset containing div pre
selector:
pre:hover:extend(div pre) {
// ... body as usually ...
}
Every page element that matches the pre:hover
selector gains all declarations assigned to div pre
selector. In that sense, extend brings inheritance into css.
The extend is either attached to a selector or placed into a ruleset. It looks like a pseudoclass with one or more comma separated selector parameters. Each selector can be followed by optionally keyword all
:
:extend(selector)
:extend(selector all)
:extend(selector1 all, selector2)
Extend attached to a selector looks like an ordinary pseudoclass with selector as a parameter. A selector can contain multiple extend clauses, but it must end after the last one.
- Extend after the selector:
pre:hover:extend(div pre)
. - Space between selector and extend is allowed:
pre:hover :extend(div pre)
. - Multiple extends are allowed:
pre:hover:extend(div pre):extend(.bucket tr)
. - This is NOT allowed:
pre:hover:extend(div pre).nth-child(odd)
. Extend must be last.
If a ruleset contains multiple selectors, any of them can have the extend keyword. Multiple selectors with extend in one ruleset:
.big-bucket:extend(.bucket), .big-bag:extend(.bag), .big-division {
// body
}
Extend can be placed into rulesets body using &:extend(selector)
syntax. Placing extend into a body is a shortcut for placing it into every single selector of that ruleset.
Extend inside a body:
pre:hover, .some-class {
&:extend(div pre);
}
is exactly the same as adding an extend after each selector:
pre:hover:extend(div pre), .some-class:extend(div pre) {
}
Extend copies selector it is attached to into rulesets containing selector from the argument. If the style sheet contains no media declarations, the selector is copied into every rulesets containing target selector.
Simple Example:
link:hover { // ruleset with target selector
color: blue;
}
.some-class:extend(link:hover) { // selector will be copied into rulesets having link:hover
margin: 1 2 3 4;
}
link:hover { // another ruleset with target selector
padding: 1 1 1 1;
}
Compiled css:
link:hover, .some-class {
color: blue;
}
.some-class {
margin: 1 2 3 4;
}
link:hover, .some-class {
padding: 1 1 1 1;
}
Extends acts on selectors in compiled css. If it is placed on nested selector, only extending selector is combined with outer selector. Target selector is not combined with it. Following:
.outer {
div { // compiles into .outer div - will NOT be extended
color: red;
}
.inner:extend(div) { // extend on nested selector
color: red;
}
}
compiles into:
.outer div { // target selector lack `all` keyword - nothing was extended
color: red;
}
.outer .inner { // there is not div selector - nothing was extended
color: red;
}
Extend is able to match nested selectors. Following less:
.bucket {
tr { // nested ruleset with target selector
color: blue;
}
}
.some-class:extend(.bucket tr) { } // nested ruleset is recognized
is compiled into:
.bucket tr, .some-class {
color: blue;
}
Extend looks for exact match between selectors. It does matter whether selector uses leading start or not. It does not matter that two nth-expressions have the same meaning, they need to have to same form in order to be matched. The only exception are quotes in attribute selector, less knows they have to same meaning and matches them.
Leading star does matter. Selectors *.class
and .class
are equivalent, but extend will not match them:
*.class {
color: blue;
}
.noStar:extend(.class) {} // this will NOT match the *.class selector
compiles into:
*.class {
color: blue;
}
Order of pseudoclasses does matter. Selectors link:hover:visited
and link:visited:hover
match the same set of elements, but extend treats them as different:
link:hover:visited {
color: blue;
}
.selector:extend(link:visited:hover) {}
compiles into:
link:hover:visited {
color: blue;
}
Nth expression form does matter. Nth-expressions 1n+3
and n+3
are equivalent, but extend will not match them:
:nth-child(1n+3) {
color: blue;
}
.child:extend(n+3) {} //
compiles into:
:nth-child(1n+3) {
color: blue;
}
Quote type in attribute selector does not matter, all are equivalent. Less file:
[title=identifier] {
color: blue;
}
[title='identifier'] {
color: blue;
}
[title="identifier"] {
color: blue;
}
.noQuote:extend([title=identifier]) {}
.singleQuote:extend([title='identifier']) {}
.doubleQuote:extend([title="identifier"]) {}
compiles into:
[title=identifier], .noQuote, .singleQuote, .doubleQuote {
color: blue;
}
[title='identifier'], .noQuote, .singleQuote, .doubleQuote {
color: blue;
}
[title="identifier"], .noQuote, .singleQuote, .doubleQuote {
color: blue;
}
Extend is NOT able to match selectors with variables. If selector contains variable, extend will ignore it. However, extend can be attached to interpolated selector.
Selector with variable will not be matched:
@variable: .bucket;
@{variable} { // interpolated selector
color: blue;
}
.some-class:extend(.bucket) { } // does nothing, match is not found
and extend with variable in target selector matches nothing:
.bucket {
color: blue;
}
.some-class:extend(@{variable}) { } // interpolated selector matches nothing
@variable: .bucket;
Two previous examples compile into:
.bucket {
color: blue;
}
Extend attached to interpolated selector works:
.bucket {
color: blue;
}
@{variable}:extend(.bucket) { }
@variable: .selector;
previous less compiles into:
.bucket, .selector {
color: blue;
}
Extend all replaces parts of selectors. If any part of a selector matches :extend
parameter, the extend will add a new selector with replaced matching parts.
Whatever works for :focus
should work for :hover
too:
pre:focus {
color: blue
}
:hover:extend(:focus all) {}
compiled css:
pre:focus,
pre:hover {
color: #0000ff;
}
Matching algorithm looks for complete match between elements names, classes, ids and other selector parts. Extend all will never replace only part of element or class name. The :extend(a all)
in following example will not act on caption
tag:
caption {
color: green;
}
.outerClass {
color: blue;
}
.extending:extend(a all):extend(.outer all) {} // extend will not find a match
compiles into:
caption {
color: green;
}
.outerClass {
color: blue;
}
However, if extending selector starts by element name and replaces pseudo-class, the element name will be attached to whatever precedes matched pseudo-class:
input:hover {
color: green;
}
li:extend(:hover all) {}
compiles into meaningless:
input:hover,
inputli {
color: green;
}
Extend written inside media declaration should matches only selectors inside that media declaration:
@media print {
.screenClass:extend(.selector) {} // extend inside media
.selector { // this will be matched - it is in the same media
color: black;
}
}
.selector { // ruleset on top of style sheet - extend ignores it
color: red;
}
@media screen {
.selector { // ruleset inside another media - extend ignores it
color: blue;
}
}
compiles into:
@media print {
.selector, .screenClass { // ruleset inside the same media was extended
color: black;
}
}
.selector { // ruleset on top of style sheet was ignored
color: red;
}
@media screen {
.selector { // ruleset inside another media was ignored
color: blue;
}
}
Extend written inside media declaration does not match selectors inside nested declaration:
@media screen {
.screenClass:extend(.selector) {} // extend inside media
@media (min-width: 1023px) {
.selector { // ruleset inside nested media - extend ignores it
color: blue;
}
}
}
compiles into:
@media screen and (min-width: 1023px) {
.selector { // ruleset inside another nested media was ignored
color: blue;
}
}
Top level extend matches everything including selectors inside nested media:
@media screen {
.selector { // ruleset inside nested media - top level extend works
color: blue;
}
@media (min-width: 1023px) {
.selector { // ruleset inside nested media - top level extend works
color: blue;
}
}
}
.topLevel:extend(.selector) {} // top level extend matches everything
compiles into:
@media screen {
.selector, .topLevel { // ruleset inside media was extended
color: blue;
}
}
@media screen and (min-width: 1023px) {
.selector, .topLevel { // ruleset inside nested media was extended
color: blue;
}
}