Stylesheets are full of repetition. A button and a link styled to look like a button share most of their rules. Three kinds of notification message differ only in colour. Without a way to share that common ground, you end up writing the same declarations over and over, and every change means updating each copy by hand. SCSS inheritance, through the @extenddirective, lets one selector take on the styles of another so the shared rules live in a single place.
What Inheritance Means Here
@extend tells one selector to inherit all the styles of another. Instead of repeating a block of properties, you point one selector at another and SCSS handles the sharing when it compiles your code. The benefit is the usual one: write the common styles once, and any change to them propagates automatically to everything that extends them.
There are two ways to use it. You can extend an existing class, or you can extend a placeholder, which is a special construct that exists only to be inherited from. The difference between the two matters more than it first appears.
Extending a Class
The most direct form points one class at another:
.message { padding: 10px; border: 1px solid #ccc; background-color: #f5f5f5;}.success-message { @extend .message; border-color: green;}.error-message { @extend .message; border-color: red;}
Both .success-message and .error-message inherit everything from .message and then add their own border colour. The catch is that .message itself remains in the compiled CSS as a standalone class, whether or not you ever use it in your HTML. If the base class is only meant to be a foundation for others, that leftover rule is clutter.
Extending a Placeholder
Placeholders solve that problem. A placeholder is written with a % prefix and never appears in the compiled output on its own. It only contributes styles to the selectors that extend it:
%button-base { padding: 10px 20px; border: none; border-radius: 4px;}.btn-primary { @extend %button-base; background-color: blue; color: white;}.btn-secondary { @extend %button-base; background-color: gray; color: white;}
Both buttons inherit the shared base, but %button-base itself never compiles into a .button-base rule that nobody uses. This is the preferred approach whenever the base styles exist purely to be shared rather than applied directly to elements.
What Actually Happens When It Compiles
It helps to understand what @extend does under the hood, because it is not the same as copying the styles into each selector. Instead, SCSS merges the selectors that share a base into a single CSS rule. The base properties are written once, and the selectors that extend them are grouped together in front of that single rule.
This keeps the output lean, but it has a consequence worth keeping in mind. Because selectors get merged, @extend can affect specificity and the order of the cascade in ways that are not always obvious from reading the SCSS. When inheritance chains start interacting, a selector merged into a group can inherit behaviour you did not intend. This is why deep or tangled inheritance is a common source of hard-to-debug CSS.
Keeping It Manageable
Reach for placeholders when the base styles are meant to be extended rather than used directly. They keep the compiled CSS free of rules nobody references.
Avoid deep inheritance hierarchies. A placeholder extended by a class that is itself extended by another class quickly becomes difficult to trace. When you find yourself building chains like that, it is usually a sign the structure needs rethinking.
Pay attention to specificity. Because @extend merges selectors, the resulting specificity may not match what you would expect from the SCSS alone. Test the compiled result rather than assuming the styles apply in the order you wrote them.
Know when a mixin is the better tool. @extend shares a fixed set of styles. When you need to generate styles based on parameters, or when you want each selector to get its own independent copy of the rules rather than being merged into a shared group, a mixin is the right choice instead.
Putting It Together
A notification component is a clean example of where inheritance earns its place. Several message types share a structure and differ only in colour:
%message-base { padding: 15px; margin: 10px 0; border: 1px solid; border-radius: 4px; font-family: Arial, sans-serif;}.message-success { @extend %message-base; background-color: #dff0d8; border-color: #3c763d; color: #3c763d;}.message-warning { @extend %message-base; background-color: #fcf8e3; border-color: #8a6d3b; color: #8a6d3b;}.message-error { @extend %message-base; background-color: #f2dede; border-color: #a94442; color: #a94442;}
Every message type inherits the shared layout, spacing, and typography from %message-base, while each applies its own colour scheme. The structural rules are defined once. If the padding or border radius needs to change across all messages, you edit a single block and every variant follows.
The Takeaway
SCSS inheritance is about removing duplication without scattering your shared styles across the stylesheet. Placeholders keep the compiled output clean by contributing styles only where they are extended. The one thing to stay mindful of is that @extend merges selectors rather than copying styles, so it can quietly affect specificity and the cascade. Used with restraint, on shallow hierarchies and clearly named placeholders, it makes a stylesheet easier to maintain. Used carelessly, it produces exactly the kind of complex, surprising selectors it was supposed to help you avoid.
See you soon
[…] a function, which hands back a single value, a mixin outputs full property declarations. Unlike inheritance, which merges selectors into a shared rule, a mixin includes its styles directly into each selector […]
[…] your styles into small, single-purpose modules. The smaller and more focused each object is, the more places it can be reused. A class that does […]