What Are SCSS Mixins?
In plain CSS, repeating the same set of declarations across multiple selectors is inevitable — think of vendor-prefixed properties, flex centering patterns, or responsive breakpoint rules. SCSS mixins solve this by letting you define a reusable block of CSS once and include it anywhere with @include.
A mixin is defined with @mixin and called with @include. When Sass compiles your SCSS, every @include is replaced with the full CSS block from the mixin definition. The result is perfectly standard CSS — no runtime overhead.
Basic @mixin and @include Syntax
Here's the simplest possible mixin — a flex centering utility:
// Define the mixin
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}
// Use it
.hero {
@include flex-center;
height: 100vh;
}
.card {
@include flex-center;
padding: 2rem;
}
Compiled CSS output:
.hero {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}
.card {
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
Mixins with Arguments
Mixins become truly powerful when they accept arguments, making them dynamic rather than static.
@mixin button($bg-color, $text-color, $padding: 0.75rem 1.5rem) {
display: inline-block;
background-color: $bg-color;
color: $text-color;
padding: $padding;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
transition: opacity 0.2s ease;
&:hover {
opacity: 0.85;
}
}
// Include with arguments
.btn-primary {
@include button(#3b82f6, #fff);
}
.btn-danger {
@include button(#ef4444, #fff);
}
.btn-sm {
@include button(#6b7280, #fff, 0.4rem 0.9rem);
}
The third argument $padding has a default value of 0.75rem 1.5rem, so you only need to pass it when you want a different size.
Default Argument Values
Default arguments make mixins flexible without requiring callers to provide every value:
@mixin shadow($color: rgba(0, 0, 0, 0.1), $blur: 10px, $spread: 0, $x: 0, $y: 4px) {
box-shadow: $x $y $blur $spread $color;
}
.card {
@include shadow; // Uses all defaults
}
.elevated {
@include shadow(rgba(0, 0, 0, 0.25), 24px, -2px, 0, 8px);
}
.colored-shadow {
@include shadow(rgba(59, 130, 246, 0.4), 20px);
}
Keyword Arguments
When a mixin has many parameters, you can pass them by name to avoid confusion about order:
@mixin position($type, $top: auto, $right: auto, $bottom: auto, $left: auto) {
position: $type;
top: $top;
right: $right;
bottom: $bottom;
left: $left;
}
.overlay {
@include position(fixed, $top: 0, $left: 0, $right: 0, $bottom: 0);
}
.tooltip {
@include position(absolute, $top: -40px, $left: 50%);
}
The @content Block: Mixins That Wrap Content
The @content directive lets you pass an entire block of CSS into a mixin. This is perfect for media query mixins:
// Define breakpoint mixins
@mixin mobile {
@media (max-width: 767px) {
@content;
}
}
@mixin tablet {
@media (min-width: 768px) and (max-width: 1023px) {
@content;
}
}
@mixin desktop {
@media (min-width: 1024px) {
@content;
}
}
// Use them
.hero-title {
font-size: 3rem;
@include mobile {
font-size: 1.75rem;
}
@include tablet {
font-size: 2.25rem;
}
}
Compiled output:
.hero-title {
font-size: 3rem;
}
@media (max-width: 767px) {
.hero-title {
font-size: 1.75rem;
}
}
@media (min-width: 768px) and (max-width: 1023px) {
.hero-title {
font-size: 2.25rem;
}
}
Responsive Mixin with a Breakpoint Map
A more advanced pattern uses a Sass map to drive breakpoints from a single mixin:
$breakpoints: (
'sm': 576px,
'md': 768px,
'lg': 1024px,
'xl': 1280px,
'2xl': 1536px,
);
@mixin respond-to($bp) {
$value: map.get($breakpoints, $bp);
@if $value == null {
@warn "Unknown breakpoint: #{$bp}.";
} @else {
@media (min-width: $value) {
@content;
}
}
}
// Usage
.container {
padding: 0 1rem;
@include respond-to('md') {
padding: 0 2rem;
max-width: 1200px;
margin: 0 auto;
}
}
Flex Mixin for Layout
A flexible layout mixin that accepts direction and alignment:
@mixin flex(
$direction: row,
$justify: flex-start,
$align: stretch,
$wrap: nowrap,
$gap: 0
) {
display: flex;
flex-direction: $direction;
justify-content: $justify;
align-items: $align;
flex-wrap: $wrap;
gap: $gap;
}
.header {
@include flex(row, space-between, center, nowrap, 1rem);
}
.card-grid {
@include flex(row, flex-start, stretch, wrap, 1.5rem);
}
.sidebar-menu {
@include flex(column, flex-start, stretch, nowrap, 0.5rem);
}
Mixin vs Placeholder (%extend): When to Use Which
SCSS also has %placeholder selectors used with @extend. Understanding the difference helps you choose the right tool:
- @mixin + @include — Copies the CSS block into each selector. Great when you need arguments or the content varies per call.
- %placeholder + @extend — Groups selectors together in the output (more DRY output). Best for static, argument-free shared styles.
// Placeholder approach
%card-base {
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.product-card {
@extend %card-base;
padding: 1rem;
}
.profile-card {
@extend %card-base;
padding: 1.5rem;
}
// Compiled output — selectors are grouped:
// .product-card, .profile-card { border-radius: 8px; ... }
Real-World: Typography Mixin
A practical mixin for consistent typography across headings:
@mixin heading($size, $weight: 700, $line-height: 1.2) {
font-size: $size;
font-weight: $weight;
line-height: $line-height;
color: var(--heading-color);
letter-spacing: -0.02em;
}
h1 { @include heading(2.5rem); }
h2 { @include heading(2rem); }
h3 { @include heading(1.5rem, 600, 1.3); }
h4 { @include heading(1.25rem, 600, 1.4); }
h5 { @include heading(1rem, 500, 1.5); }
You can inspect the compiled output of your SCSS mixins using the CSS Formatter on htmlcsseditor.com, which makes it easy to read and understand the generated CSS structure.
Mixin Best Practices
- Keep mixins focused on a single responsibility — don't cram unrelated styles into one mixin.
- Use default argument values for optional parameters so callers aren't burdened with boilerplate.
- Document complex mixins with a comment block explaining parameters.
- Avoid mixins that generate large amounts of CSS if called frequently — consider
@extendfor static shared styles. - Store all mixins in a
_mixins.scsspartial and import it at the top of your main file with@use 'mixins'.