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.

SituationBetter choice
Styles never vary between callers@extend with a % placeholder
Styles need arguments or per-call variation@mixin
Extending across separate @use'd filesRequires careful setup, often trickier
Want smaller, grouped selector output@extend
Note: @extend cannot easily reach across module boundaries set up with @use unless the placeholder is specifically forwarded, and extending inside deeply nested media queries can produce surprising selector groupings. When in doubt, reach for a mixin instead.
Note: A useful rule of thumb: use % placeholders for shared visual roles like %button-reset or %visually-hidden, and reserve real class extension for genuine variations of an existing, already-used component class.

Exercise: Sass Extend

How does @extend combine selectors, compared to how a mixin works?