Sass Extends and Inheritance — Complete Guide
In this tutorial, you will learn about Sass Extends and Inheritance. We cover key concepts, practical examples, and best practices to help you master this topic.
Sass @extend shares CSS properties between selectors by grouping them together in the compiled output, reducing repetition without duplicating declarations.
What You'll Learn
- The @extend directive and how it works
- Placeholder selectors (%) for abstract classes
- When to use @extend vs @mixin
- Chaining extends
- Limitations and specificity issues
- Best practices for inheritance
Why It Matters
- @extend produces smaller CSS output than @mixin
- Maintains the DRY principle at the selector level
- Placeholder selectors create non-rendering base classes
- Reduces the number of repeated property declarations
Real-World Use
- A message component shares base styles between .error, .warning, .success
- A button system extends a base .btn class
- A typography system uses placeholder selectors for font patterns
- A utility library uses extend for reusable style groups
flowchart LR
A[Sass @extend] --> B[Base Selector]
A --> C[Placeholder %]
B --> D[Extended Selectors]
C --> D
D --> E[Grouped CSS Output]
E --> F[.error, .warning, .success { base styles }]
Extends vs Mixins
Code Example: @extend Basics
// Base styles
.message {
padding: 1rem;
border-radius: 4px;
font-size: 1rem;
margin-bottom: 1rem;
border: 1px solid transparent;
}
// Extended selectors
.error {
@extend .message;
background: #f8d7da;
color: #721c24;
border-color: #f5c6cb;
}
.warning {
@extend .message;
background: #fff3cd;
color: #856404;
border-color: #ffeeba;
}
.success {
@extend .message;
background: #d4edda;
color: #155724;
border-color: #c3e6cb;
}
// Compiled CSS output:
// .message, .error, .warning, .success { padding: 1rem; ... }
// .error { background: #f8d7da; ... }
// .warning { background: #fff3cd; ... }
// .success { background: #d4edda; ... }
Expected output: The compiled CSS groups .error, .warning, and .success with .message as a comma-separated selector. The base styles appear once instead of being duplicated.
Code Example: Placeholder Selectors
// Placeholder selectors (defined with %, not rendered in CSS)
%button-base {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
min-height: 44px;
text-decoration: none;
}
%card-base {
background: #fff;
border: 1px solid #eee;
border-radius: 8px;
overflow: hidden;
}
// Placeholder with nested styles
%focus-visible {
outline: 3px solid #0066CC;
outline-offset: 2px;
}
// Using placeholders
.btn-primary {
@extend %button-base;
background: #0066CC;
color: #fff;
}
.btn-secondary {
@extend %button-base;
background: #6c757d;
color: #fff;
}
.featured-card {
@extend %card-base;
border-color: #0066CC;
box-shadow: 0 4px 12px rgba(0,102,204,0.15);
}
// Placeholder with extend only
.only-extend {
@extend %focus-visible;
}
// Compiled CSS:
// .btn-primary, .btn-secondary { display: inline-flex; ... }
// .featured-card { background: #fff; ... }
// .only-extend { outline: 3px solid #0066CC; ... }
// Note: %button-base, %card-base, %focus-visible do NOT appear in output
Expected output: Placeholder selectors (%) never appear in the compiled CSS. They exist only as extension points. This avoids creating unused classes in the output.
Code Example: @extend vs @mixin Comparison
// @extend approach: grouped selectors, no duplication
%flex-center {
display: flex;
align-items: center;
justify-content: center;
}
.header { @extend %flex-center; }
.footer { @extend %flex-center; }
.hero { @extend %flex-center; }
// Compiled: .header, .footer, .hero { display: flex; align-items: center; justify-content: center; }
// @mixin approach: duplicated declarations
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}
.header { @include flex-center; }
.footer { @include flex-center; }
.hero { @include flex-center; }
// Compiled:
// .header { display: flex; align-items: center; justify-content: center; }
// .footer { display: flex; align-items: center; justify-content: center; }
// .hero { display: flex; align-items: center; justify-content: center; }
// When to use each:
// @extend: Same base styles, no parameters, used in multiple places
// @mixin: Parameterized styles, or styles used with different configurations
Expected output: @extend produces more compact output for static shared styles. @mixin duplicates declarations but allows parameters and content blocks.
Code Example: Chaining and Limitations
// Chaining extends
%visually-hidden {
position: absolute;
overflow: hidden;
clip: rect(0,0,0,0);
width: 1px;
height: 1px;
}
%focus-style {
outline: 3px solid #0066CC;
outline-offset: 2px;
}
// A selector can extend multiple placeholders
.sr-only {
@extend %visually-hidden;
}
.skip-link {
@extend %visually-hidden;
@extend %focus-style;
// When focused, becomes visible
&:focus {
position: static;
overflow: visible;
clip: auto;
width: auto;
height: auto;
background: #0066CC;
color: #fff;
padding: 0.5rem 1rem;
}
}
// @extend inside media queries
// LIMITATION: Cannot extend from outside a media query
// This works:
@media (min-width: 768px) {
%desktop-only { display: block; }
.sidebar { @extend %desktop-only; }
}
// This does NOT work:
// .sidebar { @extend %desktop-only; } // ERROR: can't extend across @media
Expected output: A selector can extend multiple placeholders, combining their styles. The @extend directive cannot cross media query boundaries - both the placeholder and the extending selector must be in the same context.
Common Mistakes
- Using @extend in deeply nested selectors — @extend within nested selectors can produce unexpected specificity and cascading issues.
- Overusing @extend for everything — For parameterized styles, mixins are better. Use @extend only for static shared styles.
- Extending across media queries — @extend cannot cross @media boundaries. This produces a compiler error.
- Not using placeholder selectors — Extending real classes (.class) keeps those classes in the output even if they are not used directly. Use %placeholder to avoid this.
- Creating long selector chains — Extending deeply can produce selectors like .class1.class2.class3 that are hard to override.
- @extend in @imported files — Files imported multiple times can cause duplicate extends. Use @use instead.
- Forgetting that @extend groups selectors — The compiled comma-separated selectors can be very long. Monitor compiled output size.
Practice Questions
- How does @extend differ from @include? @extend groups selectors together (no duplication). @include copies declarations each time (duplication).
- What is a placeholder selector and why use it? A placeholder selector (%) never renders in the output. It is used only as an extension point to avoid generating unused classes.
- Can you extend a selector inside a media query? You can extend within the same media query block, but you cannot extend a selector defined outside a media query from inside one.
- What is the risk of long selector chains with @extend? @extend creates comma-separated selectors that can grow very long and become hard to manage in DevTools.
FAQ
Mini Project
Build a notification component (info, success, warning, error) using @extend with placeholder selectors. Create %notification-base with common properties (padding, border-radius, flex layout, font-size, icon positioning). Create %notification-icon-base for the icon positioning. Each variant (info, success, warning, error) extends both placeholders and adds variant-specific colors. Also create a %focus-ring placeholder extended by all interactive elements. Compare the compiled CSS output size with an equivalent @mixin implementation. Verify the output has no orphaned placeholder selectors.
What's Next
Continue with Lesson 8: Sass Partials and Import to learn file organization with @use and @forward.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro