Extends and Inheritance Patterns

1. %Placeholder Selectors and Silent Classes

Feature Syntax Behavior Use Case
Placeholder Selector %placeholder-name Define but don't output to CSS Shared styles for @extend only
Silent Class Never appears in compiled CSS Only exists when extended Avoid unused CSS bloat
Extension @extend %placeholder; Copies selector to placeholder rules Share styles without duplication
No Output Unused placeholders removed Tree-shaken automatically Clean compiled CSS
Naming Convention Prefix with % Clear distinction from classes Intent communication
Scope Global by default Available anywhere in import tree Shared utilities
Private Placeholders %-private (with dash) Module-private in modern Sass Internal-only styles

Example: Placeholder selectors in practice

// Define placeholder (not output to CSS)
%button-base {
  display: inline-block;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  text-align: center;
  transition: all 0.3s;
  
  &:hover {
    transform: translateY(-2px);
  }
}

// Extend the placeholder
.btn-primary {
  @extend %button-base;
  background: #007bff;
  color: white;
}

.btn-secondary {
  @extend %button-base;
  background: #6c757d;
  color: white;
}

// CSS Output:
// .btn-primary, .btn-secondary {
//   display: inline-block;
//   padding: 10px 20px;
//   ...
// }
// .btn-primary:hover, .btn-secondary:hover {
//   transform: translateY(-2px);
// }
// .btn-primary { background: #007bff; color: white; }
// .btn-secondary { background: #6c757d; color: white; }

// Card component placeholders
%card-base {
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  padding: 1rem;
}

%card-hover {
  &:hover {
    box-shadow: 0 4px 8px rgba(0,0,0,0.2);
    transform: translateY(-2px);
  }
}

.product-card {
  @extend %card-base;
  @extend %card-hover;
  
  .title {
    font-size: 1.25rem;
    font-weight: 600;
  }
}

.user-card {
  @extend %card-base;
  // No hover effect for user cards
  
  .avatar {
    width: 50px;
    height: 50px;
    border-radius: 50%;
  }
}

// Clearfix placeholder (classic example)
%clearfix {
  &::after {
    content: "";
    display: table;
    clear: both;
  }
}

.container {
  @extend %clearfix;
  width: 100%;
}

// Text truncation placeholder
%text-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.truncated-title {
  @extend %text-truncate;
  max-width: 200px;
}
Note: Placeholders are only output if extended. If never used, they produce zero CSS, making them perfect for library code.

2. @extend Directive and Inheritance Chains

Concept Syntax Behavior Example
Basic Extend @extend .class; Adds selector to existing rules .btn { @extend .base; }
Extend Placeholder @extend %placeholder; Extends silent class Preferred method
Selector Grouping Combines selectors DRY compiled output .a, .b { rules }
Inheritance Chain A extends B extends C Transitive extension Multi-level inheritance
Complex Selectors Extends compound selectors All variations included .parent .child extended
Context Preservation Maintains selector context Pseudo-classes/elements preserved :hover, ::before
!optional Flag @extend .class !optional; Don't error if not found Conditional extension

Example: @extend directive usage

// Basic extension
.message {
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.message-success {
  @extend .message;
  border-color: green;
  background: #d4edda;
}

.message-error {
  @extend .message;
  border-color: red;
  background: #f8d7da;
}

// Compiled CSS:
// .message, .message-success, .message-error {
//   padding: 10px;
//   border: 1px solid #ccc;
//   border-radius: 4px;
// }
// .message-success { border-color: green; background: #d4edda; }
// .message-error { border-color: red; background: #f8d7da; }

// Inheritance chain
%base-button {
  display: inline-block;
  padding: 8px 16px;
  border: none;
  cursor: pointer;
}

%primary-button {
  @extend %base-button;
  background: #007bff;
  color: white;
}

.cta-button {
  @extend %primary-button;  // Gets both %primary-button AND %base-button
  font-size: 1.2rem;
  font-weight: bold;
}

// Complex selector extension
.error {
  color: red;
  
  &:hover {
    color: darkred;
  }
  
  &::before {
    content: '⚠ ';
  }
}

.validation-error {
  @extend .error;  // Gets all .error rules including :hover and ::before
  font-size: 0.875rem;
}

// Compiled output includes:
// .error, .validation-error { color: red; }
// .error:hover, .validation-error:hover { color: darkred; }
// .error::before, .validation-error::before { content: '⚠ '; }

// Optional extension (no error if not found)
.special-button {
  @extend .button-base !optional;  // Won't error if .button-base doesn't exist
  background: purple;
}

// Nested context preservation
.sidebar {
  .widget {
    padding: 1rem;
    background: white;
  }
}

.dashboard {
  .panel {
    @extend .widget;  // Becomes: .sidebar .panel { ... }
  }
}

// Multiple extends in one selector
.alert {
  @extend %card-base;
  @extend %shadow;
  @extend %rounded;
  
  padding: 1rem;
  margin: 1rem 0;
}
Warning: Extending complex selectors can create unexpected selector combinations. Prefer extending placeholders or simple classes.

3. @extend vs @include Performance Comparison

Aspect @extend @include (Mixin) Winner
CSS Output Size Smaller (selector grouping) Larger (duplicated rules) @extend
Gzip Compression Less effective (varied selectors) More effective (repeated rules) @include
HTTP/2 Impact Minimal difference Minimal difference Neutral
Flexibility Static (no parameters) Dynamic (accepts arguments) @include
Predictability Complex selector generation Predictable output @include
Debugging Harder (grouped selectors) Easier (local rules) @include
Browser Performance Slightly faster (fewer rules) Negligible difference Minimal impact
Maintenance Fragile (cascading changes) Isolated (local changes) @include
Modern Recommendation Use sparingly Preferred approach @include

Example: Output size comparison

// Using @extend
%button-base {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  font-size: 14px;
  cursor: pointer;
}

.btn-primary { @extend %button-base; background: blue; }
.btn-secondary { @extend %button-base; background: gray; }
.btn-success { @extend %button-base; background: green; }

// CSS Output (Smaller - 8 lines):
// .btn-primary, .btn-secondary, .btn-success {
//   padding: 10px 20px;
//   border: none;
//   border-radius: 4px;
//   font-size: 14px;
//   cursor: pointer;
// }
// .btn-primary { background: blue; }
// .btn-secondary { background: gray; }
// .btn-success { background: green; }

// Using @include
@mixin button-base {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  font-size: 14px;
  cursor: pointer;
}

.btn-primary { @include button-base; background: blue; }
.btn-secondary { @include button-base; background: gray; }
.btn-success { @include button-base; background: green; }

// CSS Output (Larger - 18 lines, but more predictable):
// .btn-primary {
//   padding: 10px 20px;
//   border: none;
//   border-radius: 4px;
//   font-size: 14px;
//   cursor: pointer;
//   background: blue;
// }
// .btn-secondary {
//   padding: 10px 20px;
//   border: none;
//   border-radius: 4px;
//   font-size: 14px;
//   cursor: pointer;
//   background: gray;
// }
// .btn-success {
//   padding: 10px 20px;
//   border: none;
//   border-radius: 4px;
//   font-size: 14px;
//   cursor: pointer;
//   background: green;
// }

// File size comparison (uncompressed):
// @extend: ~250 bytes
// @include: ~450 bytes

// File size comparison (gzipped):
// @extend: ~180 bytes (less compression)
// @include: ~190 bytes (better compression due to repetition)
// Difference: Negligible (~10 bytes)

// Flexibility comparison:
// ❌ @extend - cannot accept parameters
%card {
  padding: 1rem;
  border-radius: 4px;
}

// ✅ @include - dynamic parameters
@mixin card($padding: 1rem, $radius: 4px) {
  padding: $padding;
  border-radius: $radius;
}

.small-card { @include card(0.5rem, 2px); }
.large-card { @include card(2rem, 8px); }

When to Use Each

  • Use @extend: Shared static styles, semantic relationships, minimal variations
  • Use @include: Dynamic styles with parameters, predictable output, maintainability priority
  • Modern preference: @include for most cases due to flexibility and maintainability
  • Performance difference is negligible in real-world applications

4. Multiple Inheritance and Selector Optimization

Pattern Technique Output Consideration
Multiple Extends Multiple @extend in one selector Selector added to all extended rules Clean and efficient
Chained Extension A extends B, B extends C Transitive inheritance Hierarchical relationships
Selector Explosion Many selectors extending same base Long selector lists Monitor output size
Compound Selectors Extending complex selectors Multiple combined selectors Can be unpredictable
Smart Unification Sass merges related selectors Optimized grouping Automatic optimization
Specificity Control Extends don't add specificity Same specificity as extended CSS cascade friendly

Example: Multiple inheritance patterns

// Multiple extends in one selector
%flex-center {
  display: flex;
  justify-content: center;
  align-items: center;
}

%rounded {
  border-radius: 8px;
}

%shadow {
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.modal {
  @extend %flex-center;
  @extend %rounded;
  @extend %shadow;
  
  padding: 2rem;
  background: white;
}

// Chained extension
%base-element {
  margin: 0;
  padding: 0;
}

%interactive-element {
  @extend %base-element;
  cursor: pointer;
  user-select: none;
}

%button-element {
  @extend %interactive-element;
  border: none;
  background: transparent;
}

.custom-button {
  @extend %button-element;  // Gets all three: base, interactive, and button
  color: blue;
}

// Selector optimization in action
%card {
  background: white;
  padding: 1rem;
}

.product-card { @extend %card; }
.user-card { @extend %card; }
.info-card { @extend %card; }
.alert-card { @extend %card; }
.promo-card { @extend %card; }

// Compiled output (optimized):
// .product-card, .user-card, .info-card, .alert-card, .promo-card {
//   background: white;
//   padding: 1rem;
// }

// Smart unification example
.parent {
  .child {
    color: blue;
  }
}

.container {
  .item {
    @extend .child;  // Creates: .parent .container .item { color: blue; }
  }
}

// Compound selector extension (caution!)
.nav {
  li {
    a {
      color: blue;
      
      &:hover {
        color: darkblue;
      }
    }
  }
}

.sidebar {
  .link {
    @extend a;  // Complex output, multiple selector variations generated
  }
}

// Better approach - use placeholder
%link-styles {
  color: blue;
  
  &:hover {
    color: darkblue;
  }
}

.nav li a {
  @extend %link-styles;
}

.sidebar .link {
  @extend %link-styles;
}

// Compiled: .nav li a, .sidebar .link { color: blue; }
//           .nav li a:hover, .sidebar .link:hover { color: darkblue; }

5. @extend Limitations and Anti-patterns

Limitation Issue Impact Solution
Media Query Restriction Cannot extend across @media Compilation error Use mixins instead
Directive Boundary Cannot extend outside directive Scope limitations Define placeholders globally
No Parameters Static only, no variables Inflexible Use @include for dynamic
Selector Explosion Too many extends creates huge lists Large CSS output Limit extends per placeholder
Complex Selectors Unpredictable output Debugging difficulty Extend simple selectors only
Cascade Issues Order-dependent behavior Maintenance complexity Document dependencies
Cannot Extend Itself Circular extension error Compilation fails Review inheritance chain

Example: Common anti-patterns and solutions

// ❌ ANTI-PATTERN: Extending across media queries
%mobile-text {
  font-size: 14px;
}

@media (min-width: 768px) {
  .heading {
    @extend %mobile-text;  // ERROR: Cannot extend outside @media
  }
}

// ✅ SOLUTION: Use mixin
@mixin mobile-text {
  font-size: 14px;
}

@media (min-width: 768px) {
  .heading {
    @include mobile-text;  // Works!
  }
}

// ❌ ANTI-PATTERN: Extending complex nested selectors
.nav {
  ul {
    li {
      a {
        color: blue;
      }
    }
  }
}

.footer {
  .link {
    @extend a;  // Generates: .nav ul li .footer .link (confusing!)
  }
}

// ✅ SOLUTION: Use placeholder with simple selector
%link-base {
  color: blue;
}

.nav ul li a {
  @extend %link-base;
}

.footer .link {
  @extend %link-base;
}

// ❌ ANTI-PATTERN: Overusing extends (selector explosion)
%button {
  padding: 10px;
}

// 50+ selectors extending %button
.btn-1 { @extend %button; }
.btn-2 { @extend %button; }
// ... 50 more
.btn-50 { @extend %button; }

// Output: .btn-1, .btn-2, ..., .btn-50 { padding: 10px; }
// (creates very long selector lists)

// ✅ SOLUTION: Use utility class directly in HTML
.btn { padding: 10px; }
// HTML: <div class="btn btn-1">

// ❌ ANTI-PATTERN: Circular extension
%a {
  @extend %b;
}

%b {
  @extend %a;  // ERROR: Circular extension
}

// ❌ ANTI-PATTERN: Extending from multiple unrelated hierarchies
.component1 {
  .inner {
    color: red;
  }
}

.component2 {
  .section {
    .inner {
      background: blue;
    }
  }
}

.my-element {
  @extend .inner;  // Which .inner? Ambiguous!
}

// ✅ SOLUTION: Use explicit placeholders
%red-text {
  color: red;
}

%blue-background {
  background: blue;
}

.component1 .inner { @extend %red-text; }
.component2 .section .inner { @extend %blue-background; }
.my-element { @extend %red-text; }

// ❌ ANTI-PATTERN: Extending instead of composition
.button {
  padding: 10px;
  border: none;
}

.primary-button {
  @extend .button;
  background: blue;
}

.large-primary-button {
  @extend .primary-button;  // Deep inheritance chain
  font-size: 1.5rem;
}

// ✅ SOLUTION: Composition over inheritance
.button { padding: 10px; border: none; }
.button-primary { background: blue; }
.button-large { font-size: 1.5rem; }

// HTML: <button class="button button-primary button-large">
Warning: @extend cannot work across @media boundaries. Use @include for responsive styles.

6. Modern Alternatives to @extend

Alternative Technique Advantages Use Case
CSS Custom Properties CSS variables Runtime dynamic, no compilation Theming, dynamic values
Utility Classes Atomic CSS approach Reusable in HTML, no Sass needed Tailwind-style utilities
Mixins with @include Parametric reuse Flexible, predictable Most Sass patterns
Composition Pattern Multiple classes in HTML Explicit, maintainable Component-based design
CSS @layer Cascade layers Better cascade control Modern CSS architecture
PostCSS Plugins Build-time optimization Advanced processing Custom transformations
CSS-in-JS JavaScript styling Component-scoped, dynamic React/Vue components

Example: Modern alternatives in practice

// Alternative 1: CSS Custom Properties (CSS Variables)
:root {
  --button-padding: 10px 20px;
  --button-border: none;
  --button-radius: 4px;
  --button-cursor: pointer;
}

.btn-primary {
  padding: var(--button-padding);
  border: var(--button-border);
  border-radius: var(--button-radius);
  cursor: var(--button-cursor);
  background: var(--primary-color, blue);
}

// Benefits: Runtime dynamic, themeable without recompilation

// Alternative 2: Utility Classes (Tailwind approach)
// Define once, use in HTML
.p-4 { padding: 1rem; }
.rounded { border-radius: 0.25rem; }
.bg-blue-500 { background: #3b82f6; }
.text-white { color: white; }

// HTML: <button class="p-4 rounded bg-blue-500 text-white">

// Alternative 3: Mixins (Recommended for Sass)
@mixin button-base($bg-color, $text-color) {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  background: $bg-color;
  color: $text-color;
  
  &:hover {
    background: darken($bg-color, 10%);
  }
}

.btn-primary { @include button-base(#007bff, white); }
.btn-success { @include button-base(#28a745, white); }

// Alternative 4: Composition Pattern
.btn { padding: 10px 20px; border: none; cursor: pointer; }
.btn-rounded { border-radius: 4px; }
.btn-primary { background: blue; color: white; }
.btn-large { font-size: 1.2rem; padding: 12px 24px; }

// HTML: <button class="btn btn-rounded btn-primary btn-large">

// Alternative 5: CSS @layer (Modern CSS)
@layer base {
  .button {
    padding: 10px 20px;
    border: none;
  }
}

@layer components {
  .btn-primary {
    @extend .button;  // Or just add class in HTML
    background: blue;
  }
}

// Alternative 6: CSS-in-JS (React example - conceptual)
// const Button = styled.button`
//   padding: 10px 20px;
//   border: none;
//   border-radius: 4px;
//   background: ${props => props.variant === 'primary' ? 'blue' : 'gray'};
// `;

// Comparison: Shared styles across breakpoints
// ❌ @extend doesn't work
%card {
  padding: 1rem;
  background: white;
}

@media (min-width: 768px) {
  .mobile-card {
    @extend %card;  // ERROR!
  }
}

// ✅ CSS Custom Properties work
:root {
  --card-padding: 1rem;
  --card-bg: white;
}

.card {
  padding: var(--card-padding);
  background: var(--card-bg);
}

@media (min-width: 768px) {
  .mobile-card {
    padding: var(--card-padding);
    background: var(--card-bg);
  }
}

// ✅ Mixins work
@mixin card-styles {
  padding: 1rem;
  background: white;
}

.card { @include card-styles; }

@media (min-width: 768px) {
  .mobile-card { @include card-styles; }
}

Extends Summary and Recommendations

  • Use %placeholders for shared static styles (clearfix, text-truncate)
  • Prefer @include over @extend for most use cases
  • @extend cannot cross @media boundaries - use mixins instead
  • Avoid extending complex nested selectors - use simple placeholders
  • Modern CSS: Consider CSS custom properties for theming
  • Composition pattern (multiple classes) often better than inheritance
  • Monitor compiled CSS size when using many extends
  • Document inheritance chains for maintainability
Note: The Sass team recommends using @extend sparingly. Mixins with @include provide better flexibility and are easier to maintain.