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.
.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.
.red-text { color: red; } .blue-text { color: blue; } #sidebar { @extend .red-text; } div { @extend .blue-text; }
A naive author looking at the code and wondering how a <div id=sidebar>
element would be styled
might assume that it gets red text,
as an ID selector is used to @extend the .red-text class,
versus a much less specific tagname selector.
However, this is wrong—
While this may in some cases be confusing, it can also be a great benefit in some cases. For example, an author can define a lot of styles with simple, one-class (or one placeholder selector) rules, effectively ignoring specificity entirely, then apply them via longer, much more specific selectors, using @extend to invoke the behavior of the simpler rules. This can allow an author to avoid many of the specificity problems of using IDs in rules, for example.
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.
Note: This falls out of the definition automatically. It is called out separately for clarity, not because it’s a separate feature that needs to be specifically defined.
.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, .serious-error, .super-serious-error { color: red; } .serious-error, .super-serious-error { font-weight: bold; } .super-serious-error { animation: flashing 1s infinite; }
3. The Placeholder Selector %foo
The @extend 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 placeholder 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 placeholder 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.
Placeholder selectors have the same specificity as class selectors.
Or should they have slightly less, so concrete classes can reliably override? This would mean putting a fourth number into the specificity 3-tuple.
4. Acknowledgements
The editor 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.