SASS/SCSS

@extend & Inheritance

20 min Lesson 11 of 30

Understanding @extend & Inheritance in SASS

The @extend directive is one of SASS's most powerful features for sharing CSS rules between selectors. Instead of duplicating styles across multiple selectors or creating long comma-separated selector lists manually, @extend allows you to inherit properties from one selector to another, creating more efficient and maintainable stylesheets.

What @extend Does

@extend tells SASS that one selector should inherit all the styles of another selector. When SASS compiles your code, it groups the selectors together in the CSS output, rather than duplicating the properties. This results in smaller, more efficient CSS files.

Basic @extend Example

// SASS
.message {
  padding: 15px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 14px;
}

.success {
  @extend .message;
  border-color: #28a745;
  background-color: #d4edda;
  color: #155724;
}

.error {
  @extend .message;
  border-color: #dc3545;
  background-color: #f8d7da;
  color: #721c24;
}

.warning {
  @extend .message;
  border-color: #ffc107;
  background-color: #fff3cd;
  color: #856404;
}

Compiled CSS Output

/* Notice how selectors are grouped together */
.message, .success, .error, .warning {
  padding: 15px;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 14px;
}

.success {
  border-color: #28a745;
  background-color: #d4edda;
  color: #155724;
}

.error {
  border-color: #dc3545;
  background-color: #f8d7da;
  color: #721c24;
}

.warning {
  border-color: #ffc107;
  background-color: #fff3cd;
  color: #856404;
}
Note: The key difference between @extend and mixins is that @extend groups selectors together in the output, while mixins duplicate the CSS properties for each selector. This makes @extend more efficient for shared base styles.

@extend Syntax

The syntax for @extend is straightforward. You simply use @extend followed by the selector you want to inherit from:

@extend Syntax Patterns

// Extending a class
.child {
  @extend .parent;
}

// Extending multiple selectors
.child {
  @extend .parent;
  @extend .another-parent;
}

// Extending nested selectors
.container {
  .parent {
    font-size: 16px;
  }

  .child {
    @extend .parent;
    color: blue;
  }
}

// Extending with complex selectors
.button {
  padding: 10px 20px;

  &:hover {
    opacity: 0.8;
  }
}

.submit-button {
  @extend .button;
  background: green;
  color: white;
}

Placeholder Selectors (%)

One of the most powerful features of @extend is the ability to use placeholder selectors. These are selectors that begin with % instead of . or #. Placeholder selectors don't appear in the compiled CSS unless they are extended, making them perfect for creating reusable style patterns without generating unnecessary CSS.

Placeholder Selector Example

// Define a placeholder (won't appear in CSS by itself)
%button-base {
  display: inline-block;
  padding: 12px 24px;
  border: none;
  border-radius: 4px;
  font-size: 16px;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: all 0.3s ease;

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

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

  &:hover {
    background-color: #0056b3;
  }
}

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

  &:hover {
    background-color: #545b62;
  }
}

.outline-button {
  @extend %button-base;
  background-color: transparent;
  border: 2px solid #007bff;
  color: #007bff;

  &:hover {
    background-color: #007bff;
    color: white;
  }
}

Compiled CSS (No %button-base in output)

.primary-button, .secondary-button, .outline-button {
  display: inline-block;
  padding: 12px 24px;
  border: none;
  border-radius: 4px;
  font-size: 16px;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: all 0.3s ease;
}

.primary-button:hover, .secondary-button:hover, .outline-button:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}

.primary-button {
  background-color: #007bff;
  color: white;
}

.primary-button:hover {
  background-color: #0056b3;
}

.secondary-button {
  background-color: #6c757d;
  color: white;
}

.secondary-button:hover {
  background-color: #545b62;
}

.outline-button {
  background-color: transparent;
  border: 2px solid #007bff;
  color: #007bff;
}

.outline-button:hover {
  background-color: #007bff;
  color: white;
}
Tip: Use placeholder selectors instead of regular classes when you're creating styles that are only meant to be extended, not used directly in your HTML. This keeps your CSS clean and prevents unused classes from appearing in the output.

Chaining Extends

SASS allows you to chain extends, meaning a selector can extend another selector that itself extends yet another selector. SASS handles all the inheritance relationships and compiles them correctly.

Chaining @extend Example

%typography-base {
  font-family: 'Inter', sans-serif;
  line-height: 1.6;
  color: #333;
}

%heading-base {
  @extend %typography-base;
  font-weight: 700;
  margin-bottom: 1rem;
  letter-spacing: -0.02em;
}

%large-heading {
  @extend %heading-base;
  font-size: 2.5rem;
  line-height: 1.2;
}

h1 {
  @extend %large-heading;
  margin-top: 2rem;
}

.hero-title {
  @extend %large-heading;
  color: #007bff;
  text-transform: uppercase;
}

%small-heading {
  @extend %heading-base;
  font-size: 1.25rem;
  font-weight: 600;
}

h3 {
  @extend %small-heading;
}

.section-subtitle {
  @extend %small-heading;
  color: #6c757d;
}

Limitations of @extend

While @extend is powerful, it has some important limitations that you need to be aware of:

1. Cannot Extend Across Media Queries

One of the most significant limitations is that you cannot extend a selector from outside a media query into a media query, or vice versa. This is because media queries create separate contexts in CSS.

Media Query Limitation (This Won't Work)

// This will cause an error
.button {
  padding: 10px;
  background: blue;
}

@media (min-width: 768px) {
  .large-button {
    @extend .button; // ERROR: Can't extend across media queries
    font-size: 18px;
  }
}

// Correct approach: Define within the same context
.button {
  padding: 10px;
  background: blue;
}

@media (min-width: 768px) {
  .button {
    font-size: 16px;
  }

  .large-button {
    @extend .button; // This works
    font-size: 18px;
  }
}

2. Can Create Complex Selector Chains

When extending nested selectors, SASS creates all possible combinations, which can lead to unexpected and overly complex selectors in the output.

Complex Selector Example

// SASS
.container {
  .item {
    padding: 10px;
  }
}

.sidebar {
  .special {
    @extend .item;
    color: red;
  }
}

// Compiled CSS (creates unexpected selectors)
.container .item, .container .sidebar .special, .sidebar .container .special {
  padding: 10px;
}

.sidebar .special {
  color: red;
}
Warning: Be careful when extending nested selectors, as SASS will generate all possible selector combinations. This can lead to bloated CSS with selectors that will never match any elements in your HTML. Use placeholder selectors at the root level to avoid this issue.

@extend vs Mixins: When to Use Each

Both @extend and mixins allow you to reuse styles, but they work differently and are suited for different use cases:

@extend - Shares Selectors

%card {
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.product-card {
  @extend %card;
  background: white;
}

.user-card {
  @extend %card;
  background: #f8f9fa;
}

// Output: Selectors are grouped
.product-card, .user-card {
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

Mixin - Duplicates Properties

@mixin card {
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.product-card {
  @include card;
  background: white;
}

.user-card {
  @include card;
  background: #f8f9fa;
}

// Output: Properties are duplicated
.product-card {
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  background: white;
}

.user-card {
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  background: #f8f9fa;
}

Decision Guide:

  • Use @extend when:
    • You have a set of styles that are truly shared across multiple selectors
    • The styles don't need arguments or customization
    • You're working within the same media query context
    • You want smaller CSS output (grouped selectors)
  • Use mixins when:
    • You need to pass arguments or customize output
    • You're working across different media queries
    • You need conditional logic or loops
    • You want to avoid complex selector chains
    • You're generating vendor prefixes or complex CSS patterns
Note: Many SASS experts recommend using mixins over @extend in most cases, as mixins are more predictable and don't create unexpected selector combinations. Reserve @extend for simple cases with placeholder selectors.

The Controversy Around @extend

While @extend seems efficient at first glance, it has sparked debate in the CSS community. Here are the main concerns:

1. Unpredictable Selector Order

The order of selectors in the compiled CSS depends on where the original selector was defined, not where it was extended. This can lead to unexpected specificity issues.

2. Selector Explosion

Extending nested or complex selectors can create a combinatorial explosion of selectors in the output, many of which will never match any elements.

3. Gzip Compression

While @extend creates smaller uncompressed CSS, modern gzip compression is very effective at compressing repeated patterns (like those created by mixins), so the file size advantage is often negligible after compression.

4. Maintenance Challenges

When a selector is extended in many places, changing the base selector can have far-reaching effects that are hard to track.

Best Practices for @extend

// ✅ GOOD: Use placeholder selectors at root level
%btn {
  padding: 10px 20px;
  border: none;
  cursor: pointer;
}

.btn-primary {
  @extend %btn;
  background: blue;
}

// ❌ BAD: Extending nested class selectors
.nav {
  .item {
    padding: 10px;
  }
}

.sidebar {
  .link {
    @extend .item; // Creates complex selectors
  }
}

// ✅ GOOD: Keep extends simple and predictable
%reset-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.menu {
  @extend %reset-list;
}

.breadcrumb {
  @extend %reset-list;
}

// ❌ BAD: Extending across media queries
%card {
  padding: 20px;
}

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

// ✅ GOOD: Use mixins for responsive patterns
@mixin card {
  padding: 20px;
}

.special-card {
  @include card;

  @media (min-width: 768px) {
    @include card;
    padding: 30px;
  }
}

Practical Real-World Example

Building a UI Component System with @extend

// Base placeholder selectors
%reset {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

%clearfix {
  &::after {
    content: "";
    display: table;
    clear: both;
  }
}

%visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

%focus-ring {
  &:focus {
    outline: 2px solid #007bff;
    outline-offset: 2px;
  }
}

// Component base styles
%interactive-element {
  @extend %focus-ring;
  cursor: pointer;
  user-select: none;
  transition: all 0.2s ease;

  &:hover {
    opacity: 0.9;
  }

  &:active {
    transform: scale(0.98);
  }

  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
}

// Specific components
.button {
  @extend %interactive-element;
  display: inline-block;
  padding: 12px 24px;
  border: none;
  border-radius: 4px;
  font-size: 16px;
  font-weight: 600;
  text-align: center;
}

.link {
  @extend %interactive-element;
  @extend %focus-ring;
  color: #007bff;
  text-decoration: none;

  &:hover {
    text-decoration: underline;
  }
}

.checkbox {
  @extend %interactive-element;
  appearance: none;
  width: 20px;
  height: 20px;
  border: 2px solid #ccc;
  border-radius: 3px;

  &:checked {
    background-color: #007bff;
    border-color: #007bff;
  }
}

// Screen reader only text
.sr-only {
  @extend %visually-hidden;
}

// Container with clearfix
.container {
  @extend %clearfix;
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 20px;
}

Exercise 1: Message Component System

Create a message component system using @extend. Define a base placeholder selector for common message styles, then create success, error, warning, and info message variants by extending the base. Each message type should have:

  • Appropriate border color
  • Background color
  • Text color
  • An icon placeholder on the left (use ::before)

Use placeholder selectors to ensure the base styles don't appear in the compiled CSS unless extended.

Exercise 2: Card Layout System

Build a card component system using @extend:

  1. Create a %card-base placeholder with common card styles (padding, border-radius, box-shadow)
  2. Create a %card-header placeholder for card headers
  3. Create a %card-body placeholder for card content
  4. Create a %card-footer placeholder for card footers
  5. Create three card variants: .product-card, .profile-card, and .article-card, each extending the appropriate placeholders and adding their own unique styles

Exercise 3: @extend vs Mixin Comparison

Create two versions of a button system:

  1. Version A: Use @extend with a placeholder selector to create three button variants (primary, secondary, danger)
  2. Version B: Use a mixin to create the same three button variants
  3. Compare the compiled CSS output from both versions. Which produces smaller CSS? Which is more maintainable?
  4. Add a media query that changes button padding on screens wider than 768px. Which approach (extend or mixin) works better for this requirement?
Tip: When deciding between @extend and mixins, consider the maintainability and predictability of your code over minor file size differences. Modern build tools and compression techniques make the size difference negligible in most cases.