@extend & Inheritance
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;
}
@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;
}
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;
}
@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
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:
- Create a %card-base placeholder with common card styles (padding, border-radius, box-shadow)
- Create a %card-header placeholder for card headers
- Create a %card-body placeholder for card content
- Create a %card-footer placeholder for card footers
- 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:
- Version A: Use @extend with a placeholder selector to create three button variants (primary, secondary, danger)
- Version B: Use a mixin to create the same three button variants
- Compare the compiled CSS output from both versions. Which produces smaller CSS? Which is more maintainable?
- Add a media query that changes button padding on screens wider than 768px. Which approach (extend or mixin) works better for this requirement?