Episode 1 — Fundamentals / 1.7 — Working With SASS
1.7.f — Inheritance & Extends
In one sentence:
@extendlets one selector inherit all the rules of another by grouping selectors in the compiled CSS, and placeholder selectors (%name) define styles that exist purely to be extended — but extends come with pitfalls you must understand.
Navigation: ← 1.7.e — Mixins · 1.7.g — Functions & Operators →
1. How @extend works
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 0.25rem;
cursor: pointer;
}
.btn-primary {
@extend .btn;
background: #3b82f6;
color: white;
}
.btn-danger {
@extend .btn;
background: #ef4444;
color: white;
}
Compiled CSS:
.btn, .btn-primary, .btn-danger {
padding: 0.5rem 1rem;
border: none;
border-radius: 0.25rem;
cursor: pointer;
}
.btn-primary {
background: #3b82f6;
color: white;
}
.btn-danger {
background: #ef4444;
color: white;
}
Notice: the compiler groups selectors (.btn, .btn-primary, .btn-danger) instead of duplicating declarations. This is different from mixins, which copy declarations to each site.
2. Placeholder selectors (%)
A placeholder is a selector that starts with %. It exists only to be @extended — it never appears in the compiled CSS on its own.
%btn-base {
padding: 0.5rem 1rem;
border: none;
border-radius: 0.25rem;
cursor: pointer;
}
.btn-primary {
@extend %btn-base;
background: #3b82f6;
}
.btn-secondary {
@extend %btn-base;
background: #64748b;
}
Compiled CSS:
.btn-primary, .btn-secondary {
padding: 0.5rem 1rem;
border: none;
border-radius: 0.25rem;
cursor: pointer;
}
.btn-primary { background: #3b82f6; }
.btn-secondary { background: #64748b; }
No .btn-base class in the output — placeholders keep your CSS clean when the base style is only needed as an abstract foundation.
3. Pitfalls of @extend
Pitfall 1 — Unexpected selectors
When you extend a class, the compiler extends it everywhere that class appears, including compound selectors:
.message { padding: 1rem; }
.message .icon { fill: gray; }
.error {
@extend .message;
color: red;
}
Compiled (unexpected):
.message, .error { padding: 1rem; }
.message .icon, .error .icon { fill: gray; } /* you might not want this */
.error { color: red; }
The .error .icon rule was automatically generated. In large codebases, this can create rules you never intended.
Pitfall 2 — Media queries
@extend cannot cross media query boundaries:
.flex-center {
display: flex;
justify-content: center;
}
@media (min-width: 768px) {
.sidebar {
@extend .flex-center; // ❌ Error: cannot extend across @media
}
}
This is a hard limitation — use a mixin instead.
Pitfall 3 — Selector bloat
In a large project, extending a widely-used class can produce enormous comma-separated selector lists. The CSS remains valid but becomes hard to debug.
4. When to use extend vs mixin
| Use case | Best tool | Why |
|---|---|---|
| Many selectors sharing identical static rules | @extend (with %placeholder) | Groups selectors → smaller output |
| Rules that need arguments | @mixin | Extends cannot accept parameters |
| Rules needed inside media queries | @mixin | Extends fail across @media boundaries |
Rules with @content blocks | @mixin | Extends have no content injection |
| Default choice when unsure | @mixin | More predictable, no surprise selectors |
5. Practical example — message variants
%message-base {
padding: 1rem;
border-radius: 0.5rem;
border: 1px solid;
margin-bottom: 1rem;
}
.message-success {
@extend %message-base;
background: #f0fdf4;
border-color: #22c55e;
color: #166534;
}
.message-warning {
@extend %message-base;
background: #fffbeb;
border-color: #f59e0b;
color: #92400e;
}
.message-error {
@extend %message-base;
background: #fef2f2;
border-color: #ef4444;
color: #991b1b;
}
Output is compact — the base styles are declared once with a grouped selector.
6. Chaining extends
Extends can chain — one extended selector can itself extend another:
%box { padding: 1rem; border: 1px solid #ddd; }
%rounded-box { @extend %box; border-radius: 0.5rem; }
.card {
@extend %rounded-box;
background: white;
}
Use chains sparingly — deep chains become hard to trace.
7. Key takeaways
@extendgroups selectors in the output — efficient when many selectors share identical rules.- Placeholders (
%name) are abstract selectors that only exist to be extended — they produce no standalone CSS. - Extends have pitfalls: unexpected selector generation, no media query crossing, and potential selector bloat.
- Mixins are the safer default — more flexible, accept arguments, work in media queries.
- Use
@extendwith%placeholderfor small, static, widely-shared rule sets where output size matters.
Explain-It Challenge
Explain without notes:
- How does
@extenddiffer from@mixinin what the compiled CSS looks like? - Why does
@extendfail inside a@mediablock? - What is a placeholder selector, and why would you use one instead of extending a regular class?
Navigation: ← 1.7.e — Mixins · 1.7.g — Functions & Operators →