4 time-saving Sass functions you should be using

Roughly a 6 minute read by Simon

Sass2

When CSS preprocessors arrived on the scene they were a breath of fresh air and provided us with a suite of incredibly useful abilities way above those included in ‘vanilla’ CSS. Over the years, the UX team at Engage have developed a library of functions and mixins which we use on a daily basis. Here are some of our favourites.

Maintaining a list of z-indexes

Keeping track of all the various element layers in a complex web build is tricky, especially if you need to add a new layer between two that already exist. For example, you have one component with a z-index of 10, another with a z-index of 9. If you then want to add a new element between those existing layers, you’ll then need to manually update your z-indexes.

One solution is to use ‘magic numbers’ that you (think) you’ll know will never clash. This isn’t really sustainable in the long term, especially if you’re working with a team.

Our solution (which we’ve briefly mentioned before) is to store all the layers in a list and use a function to dynamically generate a z-index based on the position in that list. Using this approach, you can future-proof your code and simplify z-index declarations.

// Before (manual declarations)
.modal { z-index: 9999; } 
.site-nav { z-index: 210; } 
.site-header { z-index: 200; } 
.site-content { z-index: 100; } 
.site-footer { z-index: 50; }
 
 
// After (using functions and lists)
 
// Function
@function z($name, $z-indexes-list: $z-indexes) {
 @if index($z-indexes-list, $name) {
 @return (length($z-indexes-list) - index($z-indexes-list, $name)) + 1;
 } @else {
 @warn 'There is no item "#{$name}" in this list; choose one of: #{$z-indexes-list}';
 
 @return null;
 }
}
 
// List
$z-indexes: (
 'modal',
 'site-nav',
 'site-header',
 'site-content',
 'site-footer'
);
 
// Example
.modal { z-index: z(modal); }
.site-nav { z-index: z(site-nav); }
.site-header { z-index: z(site-header); }
.site-content { z-index: z(site-content); }
.site-footer { z-index: z(site-footer; }
 
// Output
.modal { z-index: 5; }
.site-nav { z-index: 4; }
.site-header { z-index: 3; }
.site-content { z-index: 2; }
.site-footer { z-index: 1; }

Once this is in place you can add new elements and reorder existing ones simply by updating the list. Not only is this a cleaner approach as you can avoid magic numbers, it’s also more maintainable across a team; your colleagues can check the project’s variables file for the entire stack.

If you ensure that every z-index in your project uses the z-index list and our function, you’ll never get in a pickle again.

Half and negative values

These are a trio of incredibly simple functions which will not only keep your code a bit cleaner by removing the need for inline maths, it’ll also speed production up a little.

For the sake of these examples, we’ll assume there’s a variable $element-width with a value of 100px.

Half-numbers

This simply returns the given value divided by 2.

// Function
@function h($num) {
 @return $num / 2;
}
 
// Example
.example {
 width: $element-width;
 height: h($element-width);
}
 
// Output
.example {
 width: 100px;
 height: 50px;
}

Negative numbers

This function returns an inverted version of the given value.

// Function
@function n($num) {
 @return 0 - $num;
}
 
// Example
.example {
 top: n($element-width);
}
 
// Output
.example {
 top: -100px;
}

Half-negative numbers

Finally, combining the previous two functions, this halves the given value and inverts it. Useful for positioning elements using negative margins or transforms when you know the dimensions.

// Function
@function hn($num) {
 @return n(h($num));
}
 
// Example
.example {
 position: absolute;
 width: $element-width;
 left: 50%;
 transform: translateX(hn($element-width));
}
 
// Output
.example {
 position: absolute;
 width: 100%;
 left: 50%;
 transform: translateX(-50px);
}

These functions aren’t going to save you weeks of time in the grand scheme of things, but it will likely make things more maintainable for you and your team.

Photoshop letter spacing in CSS

Anyone who has built a design supplied in PSD format will likely have wrestled with Photoshop’s strange approach to letter spacing. This function allows you to input the letter spacing value from Photoshop and return a usable CSS value.

// Function
@function ls-basic($value) {
 @return $value / 1000 * 1em;
}
 
// Example
.button {
 letter-spacing: ls-basic(-5);
}
 
// Output
.button {
 letter-spacing: -0.005em;
}

This works great, but it does encourage inconsistency by allowing you to enter any value you like without consideration for the rest of the project’s typography. A better solution is to use a variable list and then manage your letter-spacing all in one place.

// Function
@function ls($name) {
 $value: map_get($ls, $name);
 @return $value / 1000 * 1em;
}
 
// List
$ls: (
 base: -10,
 heading: 10,
 button: 5,
 slogan: -100,
);
 
// Example
body {
 letter-spacing: ls(base);
}
 
h1,
h2,
h3 {
 letter-spacing: ls(heading);
}
 
.button {
 letter-spacing: ls(button);
}
 
.slogan {
 letter-spacing: ls(slogan);
}
 
// Output
body {
 letter-spacing: -0.01em;
}
 
h1,
h2,
h3 {
 letter-spacing: 0.01em;
}
 
.button {
 letter-spacing: 0.005em;
}
 
.slogan {
 letter-spacing: -.08em;
}

SVG background images

Using inline SVG background images can be tricky, and there are a few cross-browser issues to consider too, mostly relating to the formatting of the SVG. There are several online services and tools to convert an SVG into a format suitable for use in your CSS but what you’re left with is unwieldy and difficult to read. This function makes Sass do the heavy lifting, leaving us to write cleaner code.

Let’s use this SVG as an example:

<svg viewBox="0 0 230 230"><path fill="#00f" d="M153.4,161c-2,0-4-0.8-5.5-2.3L115,125.8l-32.9,32.9c-1.5,1.5-3.4,2.3-5.5,2.3s-4-0.8-5.5-2.3c-1.4-1.4-2.2-3.3-2.2-5.3 c0-2,0.8-3.9,2.2-5.3l33-33L71.3,82.1c-1.4-1.4-2.2-3.3-2.2-5.4s0.8-4,2.2-5.4c1.4-1.4,3.3-2.2,5.4-2.2s4,0.8,5.4,2.2l32.9,32.9 l33-33c1.4-1.4,3.3-2.2,5.4-2.2s4,0.8,5.4,2.2c1.4,1.4,2.2,3.3,2.2,5.4s-0.8,4-2.2,5.4l-33,33l33,32.9c1.4,1.4,2.2,3.3,2.2,5.4 s-0.8,4-2.2,5.4C157.3,160.2,155.4,161,153.4,161z"/></svg>

Unfortunately, we can’t just add that as the background-image URL value. We need to format it first:

.button {
 background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 230 230'%3E%3Cpath fill='%2300f' d='M153.4,161c-2,0-4-0.8-5.5-2.3L115,125.8l-32.9,32.9c-1.5,1.5-3.4,2.3-5.5,2.3s-4-0.8-5.5-2.3c-1.4-1.4-2.2-3.3-2.2-5.3 c0-2,0.8-3.9,2.2-5.3l33-33L71.3,82.1c-1.4-1.4-2.2-3.3-2.2-5.4s0.8-4,2.2-5.4c1.4-1.4,3.3-2.2,5.4-2.2s4,0.8,5.4,2.2l32.9,32.9 l33-33c1.4-1.4,3.3-2.2,5.4-2.2s4,0.8,5.4,2.2c1.4,1.4,2.2,3.3,2.2,5.4s-0.8,4-2.2,5.4l-33,33l33,32.9c1.4,1.4,2.2,3.3,2.2,5.4 s-0.8,4-2.2,5.4C157.3,160.2,155.4,161,153.4,161z'/%3E%3C/svg%3E");
}

As you can see, that took a fairly understandable bit of code and turned it into something that’s very hard to read. Thankfully, we’ve got a function for that:

@function svg-url($svg){
 //
 // Add missing namespace
 //
 @if not str-index($svg,xmlns) {
 $svg: str-replace($svg, '
 }
 //
 // Chunk up string in order to avoid
 // "stack level too deep" error
 //
 $encoded:'';
 $slice: 2000;
 $index: 0;
 $loops: ceil(str-length($svg)/$slice);
 @for $i from 1 through $loops {
 $chunk: str-slice($svg, $index, $index + $slice - 1);
 //
 // Encode
 //
 $chunk: str-replace($chunk, '"', '\'');
 $chunk: str-replace($chunk, '%', '%25');
 $chunk: str-replace($chunk, '#', '%23');
 $chunk: str-replace($chunk, '{', '%7B');
 $chunk: str-replace($chunk, '}', '%7D');
 $chunk: str-replace($chunk, '<', '%3C');
 $chunk: str-replace($chunk, '>', '%3E');
 $encoded: #{$encoded}#{$chunk};
 $index: $index + $slice;
 }
 @return url("data:image/svg+xml,#{$encoded}");
}
// Helper function to replace characters in a string
@function str-replace($string, $search, $replace: '') {
 $index: str-index($string, $search);
 @return if($index,
 str-slice($string, 1, $index - 1) + $replace +
 str-replace(str-slice($string, $index +
 str-length($search)), $search, $replace),
 $string);
}

Using this function, our Sass can now look like this, and the output will remain the same:

.button {
 background-image: svg-url('<svg viewBox="0 0 230 230"><path fill="#00f" d="M153.4,161c-2,0-4-0.8-5.5-2.3L115,125.8l-32.9,32.9c-1.5,1.5-3.4,2.3-5.5,2.3s-4-0.8-5.5-2.3c-1.4-1.4-2.2-3.3-2.2-5.3 c0-2,0.8-3.9,2.2-5.3l33-33L71.3,82.1c-1.4-1.4-2.2-3.3-2.2-5.4s0.8-4,2.2-5.4c1.4-1.4,3.3-2.2,5.4-2.2s4,0.8,5.4,2.2l32.9,32.9 l33-33c1.4-1.4,3.3-2.2,5.4-2.2s4,0.8,5.4,2.2c1.4,1.4,2.2,3.3,2.2,5.4s-0.8,4-2.2,5.4l-33,33l33,32.9c1.4,1.4,2.2,3.3,2.2,5.4 s-0.8,4-2.2,5.4C157.3,160.2,155.4,161,153.4,161z"/></svg>');
}

In summary

These functions save time. They allow you to concentrate on the bigger picture, rather than wasting your valuable time and money reinventing the wheel.

You can find out more and view our full list of functions by looking through our Laravel baseplate project on Github.