Sass @extend & Placeholders
@extend is Sass's other tool for sharing styles between selectors, alongside mixins. It aims at the same goal — avoid writing the same declarations twice — but achieves it in a completely different way: instead of copying declarations into every selector that needs them, @extend combines selectors together at the CSS output level.Basic @extend
SCSS
.alert {
padding: 0.75rem 1rem;
border-radius: 6px;
border: 1px solid transparent;
}
.alert-error {
@extend .alert;
background: #fdecea;
border-color: #f5c2c0;
color: #611a15;
}compiles to:
CSS
.alert, .alert-error {
padding: 0.75rem 1rem;
border-radius: 6px;
border: 1px solid transparent;
}
.alert-error {
background: #fdecea;
border-color: #f5c2c0;
color: #611a15;
}Rather than duplicating
.alert's three declarations into .alert-error's own rule (which is what a mixin would do), Sass rewrote the selector list of the original .alert rule to include .alert-error too. Fewer bytes of compiled CSS, at the cost of a selector that now targets two classes.Placeholder selectors: %name
A placeholder selector starts with
% instead of . and is meant purely to be extended — it never compiles to a CSS rule of its own if nothing extends it, which keeps the compiled output free of a base class you never intended to use directly in markup.SCSS
%alert-base {
padding: 0.75rem 1rem;
border-radius: 6px;
border: 1px solid transparent;
}
.alert-error {
@extend %alert-base;
background: #fdecea;
color: #611a15;
}
.alert-success {
@extend %alert-base;
background: #eafaf1;
color: #0f5132;
}compiles to:
CSS
.alert-error, .alert-success {
padding: 0.75rem 1rem;
border-radius: 6px;
border: 1px solid transparent;
}
.alert-error {
background: #fdecea;
color: #611a15;
}
.alert-success {
background: #eafaf1;
color: #0f5132;
}%alert-base itself never appears in the output — only the two real classes that extended it do, combined into one shared rule.@extend can produce large, unpredictable combined selectors
In a small example like this one, the output is easy to reason about. In a large codebase with deep partial chains, extending the same placeholder from dozens of unrelated selectors across many files can generate a single, very long combined selector list that is hard to predict just by reading any one file — and every selector in that list shares the exact same specificity and source order behavior as the others, which can produce surprising cascade results.
Many style guides now prefer mixins over @extend
This is a genuinely debated preference rather than settled fact. Some teams still like
@extend for the smaller compiled output. Others have moved to mixins for everything, valuing the predictability of duplicated-but-local declarations over Sass silently rewriting selector lists behind the scenes. If in doubt, reach for a mixin — it's the more explicit, less surprising choice for most modern codebases.@extend vs. a mixin, side by side
SCSS
// @extend: combines selectors, smaller output, less obvious behavior
%card-base {
padding: 1rem;
border-radius: 8px;
}
.card {
@extend %card-base;
}
// Mixin: duplicates declarations, larger output, fully explicit
@mixin card-base {
padding: 1rem;
border-radius: 8px;
}
.card {
@include card-base;
}@extendshares styles by rewriting the selector list of the extended rule — no duplication, but harder to trace at a glance.A placeholder (
%name) exists purely to be extended and is never output on its own if unused.@extendcan chain — extending something that itself extends something else — which is exactly where the output can get hard to predict.
Next
See Sass Partials & @use / @forward for how to organize mixins, functions, and placeholders like these across multiple files.