@extend vs Mixin

Choose the right reuse tool for the job.

Both mixins and @extend reuse styles, but they differ in output and flexibility.

Mixin duplicates declarations while extend groups selectors@include (mixin).a { padding: 8px; color: red; }.b { padding: 8px; color: red; }declarations copied into each rule@extend.a, .b { padding: 8px; color: red; }selectors grouped, declarations onceMixin: accepts arguments, safe, may repeat CSS.Extend: no arguments, smaller output, watch selector groups.
Mixins duplicate declarations; @extend merges selectors so the styles appear once.

Rule of thumb

  • Need arguments or a @content block? Use a mixin.
  • Sharing a fixed set of declarations across many selectors? Use @extend with a placeholder.
  • When unsure, prefer mixins — modern minification (gzip) handles the repetition well and mixins avoid @extend's surprises.

Example

Example · css
// Mixin: parameterized, duplicates output
@mixin pill($bg) {
  border-radius: 999px;
  background: $bg;
}
.tag { @include pill(#CC6699); }

// Extend: fixed styles, shared once via placeholder
%pill-base { border-radius: 999px; padding: 2px 10px; }
.chip { @extend %pill-base; background: #6699CC; }

When to use it

  • A developer uses a mixin for button variants because each variant needs different argument values (colors), while using @extend for a shared focus ring because it is always identical.
  • A team prefers mixins over @extend in component libraries because @extend cannot cross file boundaries when using @use, but mixins can.
  • A developer reviews their codebase and converts three identical %card rules to a mixin when they discover the rules needed to accept a border-color argument.

More examples

When to use @extend

Identical output with no variation is the ideal case for @extend; the placeholder groups both selectors into one CSS rule.

Example · css
// Good @extend candidate — identical output, no arguments needed
%visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.skip-link  { @extend %visually-hidden; }
.sr-label   { @extend %visually-hidden; }

When to use a mixin

Because each call produces different property values, a mixin is the right tool — @extend cannot accept arguments.

Example · css
// Good mixin candidate — output varies by argument
@mixin button($bg, $text: white) {
  background: $bg;
  color: $text;
  padding: 0.5rem 1rem;
  border-radius: 4px;
}

.btn-primary { @include button(#2563eb); }
.btn-danger  { @include button(#dc2626); }

Extend fails across @use modules

Shows that @extend cannot cross @use module boundaries, making mixins the only option for shared styles between separate files.

Example · css
// _base.scss
%card { border: 1px solid #ccc; }

// components.scss — this will ERROR
@use 'base';
.widget { @extend base.%card; } // Not valid cross-module extend

// Solution: use a mixin instead
@mixin card { border: 1px solid #ccc; }
.widget { @include card; }       // Works fine

Discussion

  • Be the first to comment on this lesson.