TH The Sass Way
Sass & SCSS

Sass Mixins vs Functions: When to Use Each

Sass Mixins vs Functions: When to Use Each
tldrUse a Sass mixin when you want to inject a block of CSS declarations into a rule, invoked with @include. Use a function when you want to compute and return a single value, ended with @return. If the reusable thing outputs property: value pairs, it's a mixin; if it calculates a value like a color, length, or number, it's a function.

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.

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:

Quick Decision Checklist

Ask these in order:

  1. Does the reusable thing output property: value declarations? → Mixin.
  2. Do you need to wrap a @media query or pass in a block of styles? → Mixin (with @content).
  3. Are you computing a single value — a length, color, or number — to drop into a declaration? → Function.
  4. Do you need a build-time guard or error on bad input? → Function (with @error).
  5. 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.

FAQ

What is the difference between a mixin and a function in Sass?

A mixin outputs a block of CSS declarations and is placed inside a selector with @include. A function computes and returns a single value using @return, which you then use inside a declaration. Mixins produce styles; functions produce values. Mixins can also accept a @content block and emit media queries, which functions cannot do.

When should I use a Sass mixin?

Reach for a mixin whenever the reusable thing is a group of CSS declarations: property clusters like flex centering, parameterised component styles like button variants, or breakpoint helpers that wrap a @media query using @content. If you find yourself copy-pasting several declarations together, a mixin removes the duplication.

What is a Sass function example?

A common example is a pixel-to-rem converter: @function rem($px, $base: 16px) { @return math.div($px, $base) * 1rem; }. You call it inside a value, like font-size: rem(32px), which returns 2rem. Functions are ideal for unit math, reading values from design-token maps, and any calculation you reuse across declarations.

What is the difference between @include and @return in Sass?

@include calls a mixin and pastes its declarations into the current selector. @return sits inside a function and hands back a single computed value. A function must reach a @return on every path, and using @return inside a mixin is an error. Think of @include as dropping styles in and @return as giving a value back.

Can a Sass function output CSS declarations?

No. A function can only compute and return one value with @return; it cannot emit property: value pairs or wrap a @media block. If you need to output declarations or use a @content block, you must use a mixin. Functions are used inside a value, not as a standalone block within a selector.

Do CSS custom properties replace Sass mixins and functions?

Not fully. CSS custom properties handle runtime, cascading values like theme switches that Sass cannot, since Sass compiles away before the browser runs. But custom properties can't loop, do build-time unit math, or emit declaration blocks. Most teams use both: Sass mixins and functions to generate CSS, custom properties for values that change live.