1. Introduction
Sometimes, when designing a page, an author might create some styles for a given type of element, such as "error" messages. Later, they might realize they need to create a "subclass" of the first type, such as a "serious error" message, which is styled the same way as "error", but with a few tweaks to make it more distinctive. Currently, CSS does not have a good way to handle this.
If the author has control over the HTML, they can declare that every element with a class of "serious-error" must also have a class of "error". This, however, is error-prone-- it’s easy to forget to add the "error" class to an element, causing confusing styling issues, and any scripting that creates or manipulates error elements has to know to maintain the states properly (for example, any time they remove the "error" class, they have to remember to check for and remove "serious-error" as well).
Alternately, this can be handled in the CSS-- every time a style rule contains a .error selector, the selector can be duplicated with .serious-error replacing it. This, too, is error-prone: it’s easy for typos or inattention to cause the duplicated selectors to drift apart, and it’s easy, when adding new .error rules, to forget to duplicate the selector.
The @extend rule, defined in this specification, fixes this common issue. It allows an author to declare that certain elements, such as everything matching .serious-error, must act as if they had the necessary features to match another selector, such as .error.
.error { color: red; border: thick dotted red; } .serious-error { @extend .error; font-weight: bold; }
Now an element like <div class=serious-error>
will have red
text and border, just like elements with class=error
, but will
also use bold text.
This allows authors to write simple HTML, applying either class=error
or class=serious-error
to elements as
appropriate, and write simple CSS, creating style rules that just mention .error or .serious-error, secure in the knowledge that the former rules
will also apply to serious errors.
2. The @extend Rule
The @extend rule declares that a matched element must act as if it had the necessary qualities to match another specified selector. Its syntax is:
@extend <compound-selector>;
The @extend rule is only allowed inside of style rules. In any other context, an @extend rule is invalid. An @extend rule modifies the way that selector matching works for the elements matched by the style rule the @extend selector is inside of, known as the extended elements for that rule.
The argument to @extend is the extension selector. The rule’s extended elements must, for the purpose of determining if selectors match them, act as if they had the necessary features/state/etc to match the extension selector, in addition to their pre-existing features/state/etc.
.serious-error { @extend .error; }
All elements matching the .serious-error selector must act as if they also had an "error" class for the purpose of matching selectors, regardless of what their actual set of classes is.
Should this only affect selectors in CSS, or should it affect all APIs
using selectors? Dunno which is saner for browsers; probably all selector-based
APIs. Do other query APIs, like getElementsByTagName()
, rely on the same
machinery? If so, should we generalize this to allow host languages to declare
arbitrary querying APIs to be "selector-ish"?
The @extend rule only affects the extended elements as long as the rule it’s inside of matches them.
.error { color: red; } @media (width > 600px) { .serious-error { @extend .error; font-weight: bold; } .error { width: 100%; } }
Then the .serious-error elements only act as if they have an error
class when the page’s width is greater than 600px.
.error { color: red; } @media (width > 600px) { .serious-error { color: red; width: 100%; font-weight: bold; } .error { width: 100%; } }
.my-button { @extend button; }
Any elements with class=my-button
receive the same styling as
actual button elements, as if they had a tagname of button in addition to their normal tagname.
Similarly, in the following code:
.perma-pressed-button { @extend .button:active; }
Any .perma-pressed elements are styled as if they were :active, so that any styling applied to "pressed" buttons via :active rules applies to them as well.
2.1. @extend Chaining
Multiple @extend rules can be "chained" with one rule adding certain qualities to an element, which cause another style rule containing an @extend to match.
.error { color: red; } .serious-error { @extend .error; font-weight: bold; } .super-serious-error { @extend .serious-error; animation: flashing 1s infinite; }
is equivalent to the following code without @extend:
.error { color: red; } .serious-error { color: red; font-weight: bold; } .super-serious-error { color: red; font-weight: bold; animation: flashing 1s infinite; }
3. The Functional Selector %foo
A functional selector (e.g. %foo) exists to be replaced via @extend. The rule originates in CSS preprocessors, such as SASS. Experience with those tools shows that it’s often useful to define generic, "functional" sets of styles that don’t apply to any elements directly, then use @extend to give that behavior to semantic classnames which are more meaningful within their project.
.media-block { overflow: auto; } .media-block > img { float: left; } ... .image-post { @extend .media-block; ... /* additional styles to tweak the display */ }
However, this also carries the possibility of confusion. In the above example, .media-block is just used to give a name to the pattern, so that other
rules can @extend it. It’s not meant to be used in a document-- there
shouldn’t be any elements with class=media-block
-- but this isn’t
obvious from the code. It’s easy for later maintainers of the file to
accidentally use .media-block directly on an element, and modify it for
their own uses (after all, if they search the codebase, they’ll find no
elements on the page using it!), perhaps accidentally breaking elements using
it in @extend.
To avoid situations like this, and make it more clear that one is developing a "generic"/"functional"/"structural" set of styles, the functional selector can be used. Its syntax is similar to a class selector, but is prefixed by a % (U+0025 PERCENT SIGN) rather than a period.
%media-block { overflow: auto; } %media-block > img { float: left; } ... .image-post { @extend %media-block; }
Host languages must not provide any way for an element to match a functional selector; the only way for an element to match one is by using an @extend rule. This ensures that no element will ever directly match the styles using one, even by accident, and it can’t be accidentally reused for an element directly.
Functional selectors have no specificity, the same as universal selectors.
4. Acknowledgements
The editors would like to thank the following people:
-
Nicole Sullivan for first coming up with the idea for @extend.
-
Chris Eppstein and Natalie Weizenbaum for developing and programming the modern incarnation of @extend in Sass.
-
The Sass community, for using @extend so extensively that its lack in CSS couldn’t be ignored.