The SCSS functions guide explains the idea: a @function computes and returns a single value, a number, a colour, a string, without emitting any CSS by itself. That makes functions the tool for centralising calculations, so a change in one place updates every stylesheet that uses it. This workbook builds that skill in ten runnable steps, from your first @return through a px-to-rem converter, colour helpers, and validation with @error. Where an example produces something visible, the HTML is included; for the pure-calculation steps, watch the compiled styles.css to see what the function returned.
1. Your first function
A function takes parameters, computes, and hands the result back with @return. Nothing is output until you call it inside a declaration.
// styles.scss@function scale-value($n, $factor) { @return $n * $factor;}.card { padding: scale-value(8px, 2); // 16px margin: scale-value(8px, 3); // 24px}
<html lang="en"><head> <meta charset="utf-8"> <link rel="stylesheet" href="styles.css"></head><body style="margin:0;font-family:sans-serif"> <div class="card" style="background:#e8eef5">Padding 16px, margin 24px, both from one function.</div></body></html>
Open the compiled styles.css and you will find plain padding: 16px; margin: 24px;. The function ran at compile time and only its return values made it into the CSS. That is the defining difference from a mixin, which outputs whole rule blocks.
2. Default parameter values
Give a parameter a default and callers can omit it. This keeps the common case short while allowing overrides.
// styles.scss@function spacing($multiplier, $base: 8px) { @return $base * $multiplier;}.tight { padding: spacing(1); } // 8px, uses the default base.roomy { padding: spacing(3); } // 24px.custom { padding: spacing(2, 10px); } // 20px, overrides the base
<html lang="en"><head> <meta charset="utf-8"> <link rel="stylesheet" href="styles.css"></head><body style="margin:0;font-family:sans-serif"> <p class="tight" style="background:#e8eef5">spacing(1) = 8px</p> <p class="roomy" style="background:#d9e2ec">spacing(3) = 24px</p> <p class="custom" style="background:#cfe0f1">spacing(2, 10px) = 20px</p></body></html>
The $base: 8px default encodes your spacing unit once. Change it to 4px in the function signature and every call without an explicit base shifts to the new scale together.
3. Functions versus mixins, side by side
Both take parameters, but they answer different questions. A function returns one value for you to place; a mixin emits a block of declarations. Seeing them together fixes the distinction.
// styles.scss@function half($n) { @return $n / 2; // returns a VALUE}@mixin card-surface($color) { background: $color; // outputs DECLARATIONS border-radius: 8px; padding: 16px;}.panel { @include card-surface(#e8eef5); // mixin: pulls in the block margin-bottom: half(32px); // function: supplies one value}
<html lang="en"><head> <meta charset="utf-8"> <link rel="stylesheet" href="styles.css"></head><body style="margin:0;font-family:sans-serif;padding:1rem"> <div class="panel">A mixin styled this box; a function computed its margin.</div></body></html>
The rule of thumb from the guide: reach for a function when you need a computed value inside a declaration, and a mixin when you need to stamp out reusable groups of declarations.
4. Safe division with sass:math
Modern Sass deprecates / for division because the slash also separates values in CSS. Use math.div from the built-in module instead, loaded with @use.
// styles.scss@use 'sass:math';@function columns-width($count, $total: 960px, $gap: 20px) { $gaps: $gap * ($count - 1); @return math.div($total - $gaps, $count);}.col-3 { width: columns-width(3); } // 306.66667px.col-4 { width: columns-width(4); } // 225px
<html lang="en"><head> <meta charset="utf-8"> <link rel="stylesheet" href="styles.css"></head><body style="margin:0;font-family:sans-serif"> <div class="col-4" style="background:#e8eef5">One of four columns in a 960px grid.</div></body></html>
@use 'sass:math' loads Sass’s maths module, and math.div is the future-proof division. The function packages a real layout calculation, column width from a total, a count and a gap, into one named, reusable formula.
5. A px-to-rem converter
The classic everyday SCSS function: write sizes in the pixels your designer gave you, output the rems your stylesheet should use.
// styles.scss@use 'sass:math';@function rem($px, $root: 16px) { @return math.div($px, $root) * 1rem;}.title { font-size: rem(28px); } // 1.75rem.body { font-size: rem(16px); } // 1rem.small { font-size: rem(12px); } // 0.75rem
<html lang="en"><head> <meta charset="utf-8"> <link rel="stylesheet" href="styles.css"></head><body style="margin:0;font-family:sans-serif;padding:1rem"> <h1 class="title">28px design, 1.75rem output</h1> <p class="body">16px design, 1rem output</p> <p class="small">12px design, 0.75rem output</p></body></html>
Dividing pixels by pixels cancels the units, and multiplying by 1rem attaches the unit you want. You keep the designer’s numbers in the source while shipping accessible, scalable rems.
6. Wrapping colour functions
Sass ships colour functions like lighten and darken. Wrapping them in your own named function centralises the decision of how your brand produces its shades.
// styles.scss$brand: #1f4e78;@function shade($color, $step) { @if $step > 0 { @return lighten($color, $step * 8%); } @return darken($color, $step * -8%);}.btn { background: shade($brand, 0); color: white; padding: 10px 16px; }.btn-hover { background: shade($brand, -1); color: white; padding: 10px 16px; }.btn-subtle { background: shade($brand, 3); padding: 10px 16px; }
<html lang="en"><head> <meta charset="utf-8"> <link rel="stylesheet" href="styles.css"></head><body style="margin:0;font-family:sans-serif;padding:1rem"> <span class="btn">Base</span> <span class="btn-hover">Darker</span> <span class="btn-subtle">Lighter</span></body></html>
Every shade of the brand colour now comes from one function with one step size. If the design team decides shades should move in 6 percent steps instead of 8, that is a one-character change rather than a hunt through the codebase.
7. String building with interpolation
Functions can return strings too. Interpolation, #{}, splices values into a string, which is handy for generating consistent class names or asset paths.
// styles.scss@function build-selector($component, $namespace: 'app') { @return "#{$namespace}__#{$component}";}@function asset-url($file, $folder: 'images') { @return url("/assets/#{$folder}/#{$file}");}// interpolation lets a returned string become a real selector.#{build-selector('banner') { background: asset-url('header.png'); padding: 16px;}
The compiled CSS contains .app__banner { background: url("/assets/images/header.png"); ... }. Both the BEM-style selector and the asset path are produced by functions, so the naming convention lives in exactly one place.
8. Aggregating a list with @each
A function can loop. Here @each walks a list of spacing values and computes their mean, showing that functions can do real data work, not just one-liners.
// styles.scss@use 'sass:math';@use 'sass:list';$spacing-scale: 4px, 8px, 12px, 16px, 24px;@function mean($values) { $sum: 0; @each $v in $values { $sum: $sum + $v; } @return math.div($sum, list.length($values));}.balanced { padding: mean($spacing-scale); } // 12.8px
The function accumulates a $sum across the list, then divides by the list’s length from the sass:list module. Any list-shaped design data, spacings, weights, breakpoints, can be summarised the same way.
9. Functions over a map: a type scale
Pair a function with a map and you get a lookup with a friendly API. This is the pattern behind every “give me size md” helper in real design systems.
// styles.scss@use 'sass:map';$type-scale: ( sm: 0.875rem, md: 1rem, lg: 1.25rem, xl: 1.563rem,);@function font-size($name) { @return map.get($type-scale, $name);}.caption { font-size: font-size(sm); }.body { font-size: font-size(md); }.heading { font-size: font-size(xl); }
<html lang="en"><head> <meta charset="utf-8"> <link rel="stylesheet" href="styles.css"></head><body style="margin:0;font-family:sans-serif;padding:1rem"> <p class="heading">Heading (xl)</p> <p class="body">Body (md)</p> <p class="caption">Caption (sm)</p></body></html>
Callers write font-size(lg) and never touch the raw numbers. Add a size to the map and it is instantly available everywhere, which is the single-source-of-truth idea from the media queries workbook applied to typography.
10. Validation and early returns with @error
The final polish: make functions fail loudly on bad input. @error stops the compile with your message, and an early @returnkeeps the happy path unindented.
// styles.scss@use 'sass:map';@use 'sass:math';$type-scale: (sm: 0.875rem, md: 1rem, lg: 1.25rem);@function font-size($name) { @if not map.has-key($type-scale, $name) { @error "Unknown size `#{$name}`. Use one of: #{map.keys($type-scale)}"; } @return map.get($type-scale, $name); // early, single return on success}@function rem($px, $root: 16px) { @if math.unit($px) != 'px' { @error "rem() expects a px value, got `#{$px}`"; } @return math.div($px, $root) * 1rem;}.safe { font-size: font-size(md); padding: rem(16px); }// .broken { font-size: font-size(huge); } // uncomment: compile fails with the list of valid sizes
Uncomment the .broken rule and the compiler stops with “Unknown size huge. Use one of: sm, md, lg”, pointing straight at the mistake. A typo caught at compile time never reaches the browser, which is exactly the fail-fast behaviour you want from shared utilities.
Work through these and you will have covered the whole article: @function and @return, parameters with defaults, the function-versus-mixin split, sass:math division, a px-to-rem converter, colour wrappers, string interpolation, list aggregation with @each, map-driven lookups, and validation with @error. The habits the guide closes on are the ones to keep. Name a function after what it returns, keep each one to a single job, and centralise every calculation you find yourself repeating, because that is what lets a one-line change update an entire stylesheet.
See you soon.
[…] SCSS Functions: 10 Code-Along Examples […]