Episode 1 — Fundamentals / 1.7 — Working With SASS

1.7.f — Inheritance & Extends

In one sentence: @extend lets 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 caseBest toolWhy
Many selectors sharing identical static rules@extend (with %placeholder)Groups selectors → smaller output
Rules that need arguments@mixinExtends cannot accept parameters
Rules needed inside media queries@mixinExtends fail across @media boundaries
Rules with @content blocks@mixinExtends have no content injection
Default choice when unsure@mixinMore 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

  1. @extend groups selectors in the output — efficient when many selectors share identical rules.
  2. Placeholders (%name) are abstract selectors that only exist to be extended — they produce no standalone CSS.
  3. Extends have pitfalls: unexpected selector generation, no media query crossing, and potential selector bloat.
  4. Mixins are the safer default — more flexible, accept arguments, work in media queries.
  5. Use @extend with %placeholder for small, static, widely-shared rule sets where output size matters.

Explain-It Challenge

Explain without notes:

  1. How does @extend differ from @mixin in what the compiled CSS looks like?
  2. Why does @extend fail inside a @media block?
  3. 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 →