Sass Extend
The @extend rule lets one selector inherit the declarations of another, so shared styles are written once and combined efficiently at compile time.
Sharing Styles With @extend
When two selectors need identical styling, @extend lets the second selector borrow everything the first one already declares. Instead of duplicating properties or grouping selectors by hand, Sass merges the selectors together in the compiled CSS.
Basic @extend usage
<!DOCTYPE html>
<html>
<head>
<style id="compiled-css"></style>
</head>
<body>
<div class="message">Base message</div>
<div class="message-success">Success message</div>
<script src="https://cdn.jsdelivr.net/npm/sass.js@0.11.1/dist/sass.sync.js"></script>
<script>
Sass.setWorkerUrl('https://cdn.jsdelivr.net/npm/sass.js@0.11.1/dist/sass.worker.js');
var sass = new Sass();
var scss = ".message {\n padding: 0.75em 1em;\n border-radius: 4px;\n border: 1px solid transparent;\n}\n\n.message-success {\n @extend .message;\n background-color: #f0fff4;\n border-color: #38a169;\n}";
sass.compile(scss, function (result) {
if (result && typeof result.text === "string") {
document.getElementById("compiled-css").textContent = result.text;
} else {
var msg = (result && (result.formatted || result.message)) || "Unknown Sass compile error";
var pre = document.createElement("pre");
pre.style.color = "#b00020";
pre.style.whiteSpace = "pre-wrap";
pre.style.fontFamily = "monospace";
pre.style.padding = "16px";
pre.textContent = "Sass compile error:\n" + msg;
document.body.prepend(pre);
}
});
</script>
</body>
</html>After compiling, Sass combines the selectors that share the base styles into one rule, rather than repeating the padding, border-radius, and border declarations separately for each. This can produce smaller CSS output than the equivalent mixin approach, since the shared properties are written only once.
Introducing Placeholder Selectors
Extending a real class like .message works, but it also outputs .message as its own standalone CSS rule, even if you never use that class directly in your markup. Placeholder selectors solve this. Written with a % instead of a . or #, a placeholder is only ever compiled into CSS when something extends it, so it never appears on its own.
Using a % placeholder
<!DOCTYPE html>
<html>
<head>
<style id="compiled-css"></style>
</head>
<body>
<div class="product-card">Product</div>
<div class="profile-card">Profile</div>
<script src="https://cdn.jsdelivr.net/npm/sass.js@0.11.1/dist/sass.sync.js"></script>
<script>
Sass.setWorkerUrl('https://cdn.jsdelivr.net/npm/sass.js@0.11.1/dist/sass.worker.js');
var sass = new Sass();
var scss = "%card-base {\n border-radius: 8px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);\n background: white;\n}\n\n.product-card {\n @extend %card-base;\n padding: 1rem;\n}\n\n.profile-card {\n @extend %card-base;\n padding: 1.5rem;\n}";
sass.compile(scss, function (result) {
if (result && typeof result.text === "string") {
document.getElementById("compiled-css").textContent = result.text;
} else {
var msg = (result && (result.formatted || result.message)) || "Unknown Sass compile error";
var pre = document.createElement("pre");
pre.style.color = "#b00020";
pre.style.whiteSpace = "pre-wrap";
pre.style.fontFamily = "monospace";
pre.style.padding = "16px";
pre.textContent = "Sass compile error:\n" + msg;
document.body.prepend(pre);
}
});
</script>
</body>
</html>- @extend .class copies styles from an existing, real selector.
- @extend %placeholder copies styles from a selector that never renders alone.
- Placeholders keep your compiled CSS free of unused helper classes.
- Extended selectors are grouped together in the output, not duplicated line by line.
- @extend works best for true 'is-a-kind-of' relationships between selectors.
@extend vs @mixin: Choosing the Right Tool
It's tempting to reach for @extend everywhere, but it isn't always the better choice. @extend combines selectors, which means all extending selectors are tied together in the source order of the file that defines the placeholder or class. @mixin instead copies the actual declarations into each selector independently, which is more flexible when values need to change per call through arguments.
Exercise: Sass Extend
How does @extend combine selectors, compared to how a mixin works?