Chepter 8: Sass @extend
1. What is @extend? (The Core Idea)
@extend tells Sass: “Make this selector behave as if it also matches that other selector.”
Instead of copying code (like @mixin does), @extend groups selectors in the compiled CSS so they share the exact same declarations.
Official one-liner from sass-lang.com (still true in Dart Sass 1.97+ in 2026):
@extend <selector> — one selector inherits the styles of another by making Sass output them together.
Key mental model:
- @mixin / @include = copy-paste code (DRY source, but repeated in output CSS)
- @extend = share the same rule (no duplication in output CSS, but creates selector relationships)
2. Basic Syntax & Example – No Placeholder First
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
// _base.scss or directly in file .message { padding: 1rem; border: 1px solid #ccc; border-radius: 4px; background: #f8f9fa; } .error { @extend .message; background: #fdd; color: #721c24; border-color: #f5c6cb; } .success { @extend .message; background: #d4edda; color: #155724; border-color: #c3e6cb; } |
Compiled CSS output (beautifully DRY):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
.message, .error, .success { padding: 1rem; border: 1px solid #ccc; border-radius: 4px; background: #f8f9fa; } .error { background: #fdd; color: #721c24; border-color: #f5c6cb; } .success { background: #d4edda; color: #155724; border-color: #c3e6cb; } |
See? .message styles are not repeated — Sass just lists all extenders in the selector list. This keeps your gzipped CSS smaller.
3. The Real Power Tool: Placeholder Selectors % (Recommended in 2026)
Often you don’t want a base class like .message to actually exist in HTML (it’s just a style blueprint).
Enter placeholder (silent / abstract class) — starts with %:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
%alert-base { padding: 1rem 1.25rem; margin-bottom: 1rem; border: 1px solid transparent; border-radius: 0.375rem; } .alert { @extend %alert-base; } .alert-success { @extend %alert-base; color: #0f5132; background-color: #d1e7dd; border-color: #badbcc; } .alert-danger { @extend %alert-base; color: #842029; background-color: #f8d7da; border-color: #f5c2c7; } |
Compiled output:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
.alert, .alert-success, .alert-danger { padding: 1rem 1.25rem; margin-bottom: 1rem; border: 1px solid transparent; border-radius: 0.375rem; } .alert-success { ... } .alert-danger { ... } |
- No .alert-base or %alert-base appears in final CSS → perfect for “abstract base styles”
- Very clean, very common in component libraries / design systems in 2026
4. @extend vs @mixin – Head-to-Head Comparison (2026 View)
| Feature / Behavior | @extend (with or without %) | @mixin + @include | When to choose? |
|---|---|---|---|
| Output CSS size | Minimal (shared selectors) | Larger (duplicates declarations) | @extend wins for identical styles |
| Accepts arguments / parameters | No | Yes (required for dynamic styles) | @mixin only |
| Can include logic/loops/if | No | Yes | @mixin only |
| Cascade / specificity impact | Can create unexpected high specificity | Predictable (just pastes where used) | @mixin safer in complex cases |
| Works across media queries? | Limited (extend must be in same context) | Full flexibility | @mixin |
| Placeholder support | Yes (%) – very clean | Not needed (mixin doesn’t output unless included) | @extend % for blueprints |
| Best for… | “Is-a” relationships (Button is-a .btn-base) | “Has-a” reusable chunks / parametric styles | See rule below |
Official Sass team rule of thumb (still valid 2026):
- Use @extend when you’re expressing that one selector is a subtype of another (semantic / “is-a”) → .alert-dangeris an.alert
- Use @mixin when you’re sharing a chunk of properties that aren’t necessarily related in meaning → flex-center utilities, clearfix, etc.
Many 2026 teams actually prefer mixins for almost everything because:
- Easier to reason about cascade
- Parameters + @content give more power
- Avoids weird specificity surprises from chained extends
But @extend % still shines for pure DRY base styles without params (e.g., reset patterns, alert families, typography scale bases).
5. Chaining Extends (Advanced – Use Carefully)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
%btn-base { padding: 0.6rem 1.2rem; border-radius: 6px; } %btn-color { color: white; } .btn { @extend %btn-base; @extend %btn-color; background: #6c5ce7; } .btn-outline { @extend %btn-base; @extend %btn-color; background: transparent; border: 2px solid currentColor; } |
→ Works, but chaining too much can create very long selector lists (performance hit, hard to debug).
6. Common Gotchas & Limitations in Dart Sass (2026)
- Only simple selectors can be extended: .class, #id, tag, :pseudo → Cannot @extend .parent .child or .btn[disabled] (Dart Sass enforces this strictly)
- Cannot @extend from another module without careful setup (with @use + @extend selector-from-module.selector)
- Inside media queries: extend stays inside unless identical extend exists outside
- Avoid extending very specific/complex selectors → leads to specificity wars
7. Quick Challenge – Try This in Your Editor
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// _alerts.scss %alert { padding: 1rem; margin: 1rem 0; border: 1px solid transparent; border-radius: 4px; } .alert-info { @extend %alert; background: #e7f3ff; color: #004085; border-color: #b8daff; } .alert-warning { @extend %alert; background: #fff3cd; color: #856404; border-color: #ffeeba; } |
Compile → see how clean the output is!
You’ve now got @extend fully under your belt — use it wisely for semantic inheritance, fall back to mixins for everything else.
Next class — want to build a full small design system using variables + nesting + mixins + @extend % together? Or dive into modern Sass color manipulation / math functions?
Just say the word — we’re on fire! 🚀
