Sass Mixins vs Functions: When to Use Each

- Mixins vs Functions in Sass: The One-Line Answer
- The Core Difference: Output vs Value
- When to Reach for a Mixin
- When to Reach for a Function
- Arguments Work the Same in Both
- @include vs @return: The Mechanics
- Can't CSS Variables Do This Now?
- Common Mistakes to Avoid
- Quick Decision Checklist
- Putting It Together
Mixins vs Functions in Sass: The One-Line Answer
Use a mixin when you want to inject a block of CSS declarations into a rule. Use a function when you want to compute and return a single value. The tell is simple: if the reusable thing outputs property: value pairs, it's a mixin, and you pull it in with @include. If it calculates something you'll drop into a value — a color, a length, a number — it's a function, and you call it with @return.
That one distinction resolves almost every "which do I reach for?" question. The rest of this guide shows why, with copy-pasteable SCSS you can drop into a real project.
The Core Difference: Output vs Value
A mixin is a reusable chunk of styles. It can emit as many declarations as you like, even whole nested rules and media queries, and you place it inside a selector with @include.
@mixin visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
.skip-link:not(:focus) {
@include visually-hidden;
}
This uses clip-path: inset(50%) rather than the older clip: rect(0, 0, 0, 0); the legacy clip property is deprecated, and clip-path is the modern, well-supported way to collapse an element to nothing while keeping it accessible.
A function takes arguments, computes something, and hands back exactly one value with @return. It emits no CSS on its own — you use its result inside a declaration.
@use "sass:math";
@function rem($px, $base: 16px) {
@return math.div($px, $base) * 1rem;
}
h1 {
font-size: rem(32px); // 2rem
}
Note the math.div() call — in modern Dart Sass you divide with math.div() after @use "sass:math";, not the / operator, which is deprecated for division. That single rule trips up a lot of older tutorials.
Here's the split at a glance:
| Aspect | Mixin | Function |
|---|---|---|
| Purpose | Reuse a block of CSS declarations | Compute and return one value |
| Defined with | @mixin name |
@function name |
| Invoked with | @include name |
name(args) in a value |
| Output | One or more property: value pairs |
A single returned value |
| Ends with | (emits CSS directly) | @return |
| Can accept arguments | Yes | Yes |
| Can accept a content block | Yes (@content) |
No |
| Lives inside a selector? | Yes | No — used inside a value |
When to Reach for a Mixin
Mixins shine whenever the repeated thing is a group of declarations, especially ones that vary by a parameter.
Vendor-neutral patterns and property groups
Anytime you'd copy-paste three or four lines together, a mixin removes the duplication:
@mixin flex-center($direction: row) {
display: flex;
flex-direction: $direction;
align-items: center;
justify-content: center;
}
.hero {
@include flex-center(column);
}
Media queries with @content
The @content directive lets a mixin wrap around arbitrary styles you pass in. This is the single most popular real-world use of mixins — a breakpoint helper:
$breakpoints: (
"sm": 640px,
"md": 768px,
"lg": 1024px,
);
@mixin respond($name) {
$width: map.get($breakpoints, $name);
@media (min-width: $width) {
@content;
}
}
.card {
padding: 1rem;
@include respond("md") {
padding: 2rem;
}
}
That requires @use "sass:map"; at the top of the file. Functions can't do this — they have no @content, and they can't emit a @media block. If you need to output rules, it has to be a mixin.
Parameterised components
Buttons, badges, and alerts that share structure but differ in color are a natural fit:
@use "sass:color";
@mixin button-variant($bg, $fg: white) {
background: $bg;
color: $fg;
border: 1px solid color.adjust($bg, $lightness: -8%);
padding: 0.5rem 1rem;
border-radius: 4px;
}
.btn-primary { @include button-variant(#2563eb); }
.btn-danger { @include button-variant(#dc2626); }
Note the color.adjust() call. In current Dart Sass the old global color functions like darken() and lighten() are deprecated in favour of the sass:color module — color.adjust($bg, $lightness: -8%) darkens by a fixed amount, and color.scale($bg, $lightness: -8%) does it proportionally. Reach for the module functions so your build stays warning-free.
When to Reach for a Function
Functions exist to produce values you can reuse across declarations. If you catch yourself doing the same arithmetic or color math in ten places, that's a function.
Unit math and conversions
The rem() helper above is the classic example. Another is a fluid spacing scale:
@use "sass:math";
@function space($step) {
@return $step * 0.25rem;
}
.stack { gap: space(4); } // 1rem
Reading from a design-token map
Functions pair beautifully with Sass maps to build a token lookup:
@use "sass:map";
$colors: (
"brand": #2563eb,
"ink": #111827,
"muted": #6b7280,
);
@function color($key) {
@if not map.has-key($colors, $key) {
@error "Unknown color: #{$key}";
}
@return map.get($colors, $key);
}
.title { color: color("ink"); }
The @error guard is a nice touch functions enable — you get a build-time failure with a clear message instead of a silent null.
Composable calculations
Because a function returns a value, you can nest calls and use results in calc() or other functions:
.sidebar {
width: calc(100% - #{space(8)});
}
Arguments Work the Same in Both
One thing that trips people up: mixins and functions share the same argument system. Everything you know about parameters applies to both, so the choice between them is never about how they take input.
Default values
Both accept default values, so callers can omit trailing arguments:
@mixin elevation($level: 1) {
box-shadow: 0 #{$level}px #{$level * 2}px rgba(0, 0, 0, 0.15);
}
.panel { @include elevation; } // uses level 1
.modal { @include elevation(4); } // overrides
Named arguments
When a mixin or function has several parameters, name them at the call site to skip the ones you don't need and keep the code readable:
@mixin border($width: 1px, $style: solid, $color: currentColor) {
border: $width $style $color;
}
.divider { @include border($color: #e5e7eb); } // width and style keep defaults
Variable arguments
An argument name followed by ... collects any number of values into a list — handy for pass-through helpers:
@mixin transition($props...) {
transition: $props;
}
.link { @include transition(color 0.2s, background 0.2s); }
The same ... syntax works for functions and for spreading a list back into a call. Because arguments behave identically, let intent — output versus value — drive your choice, never the parameters.
@include vs @return: The Mechanics
The keywords make the intent explicit, and mixing them up is the most common beginner error.
@includeis how you call a mixin. It says "paste this mixin's declarations here." It lives inside a selector.@returnis how a function hands back its value. It lives inside a@functionbody, and a function must hit a@returnon every path.
A function without @return errors out. A mixin never uses @return — if you try, Sass complains that @return is only allowed inside a function. Think of @include as "drop styles in" and @return as "give a value back."
Can't CSS Variables Do This Now?
Partly. Native CSS custom properties handle runtime, cascading values — theme switches, per-component overrides — things Sass functions can't, because Sass compiles away before the browser ever runs. But custom properties can't loop, can't do build-time math across units the way math.div() can, and can't emit whole declaration blocks. In practice teams use both: Sass mixins and functions to generate the stylesheet, custom properties for values that must change live in the browser. Our guide to CSS custom properties done right covers where each belongs, and what Sass actually is sets the broader context.
Common Mistakes to Avoid
A few errors show up again and again when developers first split logic between mixins and functions:
- Using a mixin where a function belongs. If you only need a computed value, a mixin that sets a single property is clumsy — you can't nest it inside
calc()or another value. Return the value from a function instead. - Expecting a function to output rules. Functions emit nothing on their own. Writing declarations inside a
@functionbody and hoping they render is a no-op; only the@returnvalue matters. - Dividing with
/. In current Dart Sass,width: $a / 2no longer means division and will warn. Usemath.div($a, 2)after@use "sass:math";. - Forgetting the
@userules.map.get(),math.div(), and friends live in built-in modules. Add@use "sass:map";or@use "sass:math";at the top of the file, or you'll get "undefined function" errors. - Over-parameterising. A mixin with eight arguments is usually two mixins. Keep each one focused, and lean on named arguments and defaults to keep call sites clean.
Quick Decision Checklist
Ask these in order:
- Does the reusable thing output
property: valuedeclarations? → Mixin. - Do you need to wrap a
@mediaquery or pass in a block of styles? → Mixin (with@content). - Are you computing a single value — a length, color, or number — to drop into a declaration? → Function.
- Do you need a build-time guard or error on bad input? → Function (with
@error). - Still unsure? If you'd write it after a colon in a CSS rule, it's a function; if it fills the space between the braces, it's a mixin.
A rule of thumb from real codebases: you'll write far more mixins than functions. Functions tend to be a small library of value helpers — rem(), color(), space() — while mixins carry the bulk of your reusable component and layout patterns.
Putting It Together
Mixins and functions aren't competitors; they're two halves of how Sass keeps stylesheets DRY. Functions build the vocabulary of values, and mixins assemble those values into reusable blocks. A typical file uses a function inside a mixin all the time:
@mixin card-padding($step) {
padding: space($step); // function returns the value...
} // ...mixin emits the declaration
Once the output-vs-value distinction clicks, you'll pick the right tool without thinking about it. For the full picture — setup, partials, maps, and file structure — see our complete guide to Sass, or browse more Sass and SCSS tutorials to go deeper on any piece.