Sass @extend Rule

Beginner
⏱️ 14 min read
📚 Updated: Jul 2026
🎯 5 Examples
%placeholders · !optional

What You’ll Learn

Sass @extend lets one selector inherit the styles of another by merging selectors in the compiled CSS. This page covers how extend works, placeholder selectors, !optional, module scope, when to prefer mixins, limitations, and five compiled examples.

01

Inherit

@extend

02

Merge

Selector lists

03

Silent

%placeholder

04

Optional

!optional

05

Vs mixins

When to choose

06

Practice

5 examples

What Is @extend?

Official docs: there are often cases when one class should have all the styles of another class, plus its own. Writing both classes in HTML (class="error error--serious") is cluttered and easy to forget.

Sass’s @extend rule is written @extend <selector>. It tells Sass that one selector should inherit the styles of another. When .error--serious extends .error, you can write only class="error--serious" in HTML and still get the base error styles.

  • Unlike mixins, extend does not copy properties into the current rule.
  • It updates rules that already mention the extended selector, adding the extender to those selector lists.
  • Sass extends everywhere the selector is used—including :hover and other compounds.
  • Extends are resolved after the rest of the stylesheet (including parent selectors).
💡
Beginner tip

Think of @extend as saying “this class is also that class.” Sass rewrites the CSS selectors so both share the same style rules.

📝 Syntax

styles.scss
.error {
  border: 1px #f00;
  background-color: #fdd;

  &--serious {
    @extend .error;
    border-width: 3px;
  }
}

Compiled CSS shares the base rule between .error and .error--serious, then adds the modifier-only declaration separately.

⚙️ How It Works

Official docs: when extending selectors, Sass does intelligent unification. It never generates impossible selectors (like #main#footer), interleaves complex selectors safely, trims redundancy, and handles combinators and many pseudo-classes.

  • Incompatible types are skipped (e.g. extending .info from nav will not touch p.info).
  • Cascade order follows where the extended rule appears—not where @extend is written.
  • Parent-selector nesting like .error { &__icon { … } } is not extended when you @extend .error (extends run after nesting is resolved).
💡
Selector functions

You can also use selector.unify() and selector.extend() from the Sass selector module for programmatic unification.

🚫 Placeholder Selectors

Official docs: when a style rule is only meant to be extended, use a placeholder selector—a class-like name that starts with % instead of ..

  • Selectors that include unused placeholders are omitted from CSS.
  • Extenders appear in the output in place of the placeholder.
  • Private placeholders start with - or _ and can only be extended in the same stylesheet (like private module members).
styles.scss
%message-shared {
  border: 1px solid #ccc;
  padding: 10px;
}

.message {
  @extend %message-shared;
}

🔒 Mandatory, Optional & Scope

Mandatory vs !optional

By default, if @extend matches nothing, Sass errors (protects against typos). Add !optional when a missing target should be ignored: @extend %theme-btn !optional.

Extension scope with @use

Official docs: when one stylesheet extends a selector, that extension only affects upstream modules loaded with @use / @forward (and their upstreams). That keeps extends predictable.

⚠️
Legacy @import

With deprecated @import, extensions are global across the import graph. Prefer @use for scoped, predictable extends.

⚖️ Extends or Mixins?

Official rule of thumb: use @extend when you express a relationship between semantic classes (an .error--serious is an error). For non-semantic style chunks—or anything that needs arguments—prefer a mixin.

  • Mixins can take arguments and @content; extends cannot.
  • Mixins may emit more CSS, but gzip/brotli usually handle repetition well.
  • Choose clarity over “smallest CSS” unless you have measured a problem.

🚫 Limitations

Only simple selectors

You can extend simple selectors like .info or a—not compounds like .message.info or descendants like .main .info. Write @extend .message, .info instead.

HTML heuristics

When interleaving complex selectors, Sass does not generate every possible ancestor combination (that would explode CSS size). It assumes each selector’s ancestors are mostly self-contained.

Inside @media

You may use @extend inside @media, but you cannot extend a selector that appears outside that at-rule. Keep both the extender and the extended rule in the same media context.

⚡ Quick Reference

GoalCode
Inherit a class@extend .error;
Extend several@extend .message, .info;
Silent base%btn { … } then @extend %btn;
Optional extend@extend %theme !optional;
Semantic reusePrefer @extend
Configurable chunkPrefer @mixin

Examples Gallery

Each example uses @extend at compile time. Open View Compiled CSS for verified output.

📚 Getting Started

Share styles by merging selectors.

Example 1 — Basic Extend

A BEM-style modifier inherits its block styles.

styles.scss
.error {
  border: 1px #f00;
  background-color: #fdd;

  &--serious {
    @extend .error;
    border-width: 3px;
  }
}

How It Works

HTML can use class="error--serious" alone. Sass still applies the shared border and background by listing both selectors on the base rule.

Example 2 — Extend Everywhere

Sass also extends compound uses of the selector, like :hover.

styles.scss
.error:hover {
  background-color: #fee;
}

.error--serious {
  @extend .error;
  border-width: 3px;
}

How It Works

Extending .error updates .error:hover as well, so the modifier gets the hover style without a second rule.

📈 Placeholders, Unification & Optional

Silent bases and smarter selector merging.

Example 3 — Placeholder Selectors

Share a silent base that never appears as its own class in CSS.

styles.scss
%message-shared {
  border: 1px solid #ccc;
  padding: 10px;
  color: #333;
}

.message {
  @extend %message-shared;
}

.success {
  @extend %message-shared;
  border-color: green;
}

.warning {
  @extend %message-shared;
  border-color: yellow;
}

How It Works

%message-shared never ships as a class. Extenders share one rule, then add their own border colors.

Example 4 — Intelligent Unification

Sass skips incompatible selectors and interleaves complex ones carefully.

styles.scss
.content nav.sidebar {
  @extend .info;
}

// Not extended: `p` is incompatible with `nav`.
p.info {
  background-color: #dee9fc;
}

.guide .info {
  border: 1px solid rgba(#000, 0.8);
  border-radius: 2px;
}

main.content .info {
  font-size: 0.8em;
}

How It Works

p.info stays alone. Nested cases generate interleaved selectors where needed, and main.content avoids redundant combinations.

Example 5 — Placeholders + !optional

Extend a silent button base; ignore a missing placeholder safely.

styles.scss
%btn {
  display: inline-block;
  padding: 0.5rem 1rem;
  border-radius: 4px;
}

.primary {
  @extend %btn;
  background: #036;
  color: #fff;
}

.ghost {
  @extend %btn !optional;
  border: 1px solid #036;
}

.missing-extender {
  @extend %does-not-exist !optional;
  opacity: 0.8;
}

How It Works

Both buttons share %btn. The missing placeholder does not error because of !optional—only opacity remains.

🚀 Real-World Use Cases

  • BEM modifiers.error--serious inherits .error.
  • Silent utilities%truncate, %btn shared bases.
  • Theme variants — success / warning / info message shells.
  • Design-system tokens as classes — semantic relationships, not config.
  • Library internals — private %-internal placeholders.

🧠 How Compilation Works

1

Write style rules

Nesting and other Sass features compile first.

Source
2

Resolve @extend

Sass finds every use of the extended selector.

Extend
3

Unify selectors

Intelligent merging adds extenders to matching rules.

Merge
4

CSS ships

Shared declarations appear once with selector lists.

⚠️ Common Pitfalls

  • Cascade surprises — precedence follows the extended rule’s location, not the @extend line.
  • Expecting nested & to extend — extends run after nesting is resolved.
  • Compound extends — do not write @extend .a.b; extend simple selectors.
  • Cross-media extends — cannot extend a selector defined outside the current @media.
  • Overusing extend for utilities — mixins are often clearer for non-semantic chunks.

💡 Best Practices

✅ Do

  • Extend for semantic “is-a” relationships
  • Prefer % placeholders for silent shared bases
  • Use !optional for soft theme hooks
  • Keep extends scoped with @use
  • Extend simple selectors only

❌ Don’t

  • Use extend as a substitute for every mixin
  • Extend compounds or descendant selectors
  • Extend across different @media contexts
  • Rely on global extends via legacy @import
  • Chase micro CSS savings over readable architecture

Key Takeaways

Knowledge Unlocked

Five things to remember about Sass @extend

Merge selectors for semantic inheritance—do not copy properties like a mixin.

5
Core concepts
🔀 02

Merge

selector lists

Unify
🚫 03

Silent

%placeholders

Base
🔒 04

Safe

!optional

Guard
05

Semantic

else use mixins

Choose

❓ Frequently Asked Questions

It tells Sass that one selector should inherit the styles of another. Sass updates style rules that contain the extended selector so they also include the extending selector.
Mixins copy styles into the current rule. @extend merges selectors so shared styles appear once with a selector list. Use extends for semantic relationships; use mixins for configurable style chunks.
A selector that starts with % (like %message-shared). Rules that only use placeholders are omitted from CSS until something @extends them.
Normally @extend errors if the target selector is missing. Add !optional to silently do nothing when the extended selector does not exist.
No in modern Dart Sass. Only simple selectors can be extended. Write @extend .message, .info instead of @extend .message.info.
Yes, but you can only extend selectors defined inside the same at-rule. Extending a selector that appears outside the @media is an error.
Did you know?

Official Sass docs note that most servers gzip/brotli CSS very well—so mixins that repeat text often download almost as cheaply as extends. Pick the clearer tool for your design, not just the smaller output.

Conclusion

@extend merges selectors so semantic classes can share styles without duplicating HTML classes. Reach for placeholders and !optional when you need silent bases, and prefer mixins when styles need arguments or are not a true “is-a” relationship.

Continue with @error or @mixin / @include.

Next: Sass @error

Stop compilation with clear messages when mixin or function args are invalid.

@error rule →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

5 people found this page helpful