Two Useful Sass Features and Their Limitations

By Krasimir Tsonev

I really like Sass and I use it a lot in my daily job. There are tons of useful features. However there are some things which I want to do, but can’t. There are limitations in the syntax and missing functionality. In this article I am going to share some of the ones I have found and, wherever possible, how to get around them.

Interpolation

One of the main reasons to use a CSS preprocessor is that you want a better architecture – you want to write good-looking, efficient and object-oriented CSS. The interpolation in SASS is an important part of this process. Let’s look at the following example:

$properties: (margin, padding);
@mixin set-value($side, $value) {
    @each $prop in $properties {
        #{$prop}-#{$side}: $value;
    }
}
.login-box {
    @include set-value(top, 14px);
}

This works pretty well with variables and properties. The snippet above is compiled to:

.login-box {
    margin-top: 14px;
    padding-top: 14px;
}

That’s the trivial use case of interpolation in SASS. You have strings and you use them while you set values to properties. Another useful usage is to construct a selector. For instance, something like below also works:

@mixin generate-sizes($class, $small, $medium, $big) {
    .#{$class}-small { font-size: $small; }
    .#{$class}-medium { font-size: $medium; }
    .#{$class}-big { font-size: $big; }
}
@include generate-sizes("header-text", 12px, 20px, 40px);

This produces the following CSS when compiled:

.header-text-small { font-size: 12px; }
.header-text-medium { font-size: 20px; }
.header-text-big { font-size: 40px; }

Once you discover this feature, you may immediately start thinking about super cool mixins which generate tons of code or even produce other mixins. However, that’s not exactly possible.

The first limitation, which could be removed soon is an interpolation used in a name of SASS variable.

$margin-big: 40px;
$margin-medium: 20px;
$margin-small: 12px;
@mixin set-value($size) {
    margin-top: $margin-#{$size};
}
.login-box {
    @include set-value(big);
}

If you try to use the code above you will get:

Syntax error: Undefined variable: "$margin-".

The nice #{} syntax is not available for everywhere. You can’t use it while calling a mixin either:

@mixin updated-status {
    margin-top: 20px;
    background: #F00;
}
$flag: "status";
.navigation {
    @include updated-#{$flag};
}

Trying to compile this returns:

Syntax error: Invalid CSS after "@include updated-": expected "}", was "#{$flag};"

The good news is that you can use interpolation with the @extends directive. For example:

%updated-status {
    margin-top: 20px;
    background: #F00;
}
.selected-status {
    font-weight: bold;
}
$flag: "status";
.navigation {
    @extend %updated-#{$flag};
    @extend .selected-#{$flag};
}

It’s good that this works because it gives us the power to dynamically generate classes or placeholders, which could be used later. Of course these don’t accept parameters like mixins. The compiled result of the lines above is:

.navigation {
    margin-top: 20px;
    background: #F00;
}
.selected-status, .navigation {
    font-weight: bold;
}

The SASS community is actively discussing the interpolation limitations and, who knows, maybe very soon we will be able to use those techniques.

Using list as a source for generator

Lists are really helpful if you need to pass an unknown amount of parameters to a function or mixin. However, after a couple of days work I ran into a problem which I wasn’t able to solve and which caused me to change my SASS architecture. Let’s take following code:

%margin-reset { margin: 0; }
%padding-reset { padding: 0; }
%size-reset { width: 100%; height: 100%; }
@mixin add-styles($items) {
    @each $item in $items {
        @extend %#{$item};
    }
}
.footer, .header, .login-box {
    @include add-styles((
        margin-reset,
        padding-reset,
        size-reset
    ));
}

After compilation, the CSS looks like:

.footer, .header, .login-box {
    margin: 0;
}
.footer, .header, .login-box {
    padding: 0;
}
.footer, .header, .login-box {
    width: 100%;
    height: 100%;
}

So far, so good. Now I’m able to extend as many placeholders as I want. But what about mixins? Can I, instead of placeholders, send mixin names?

The first problem is that the mixins accept parameters (if you have one which doesn’t you should consider refactoring). As you already saw, the main idea is to pass a list, which is later read element by element. Every element could be a mixin or placeholder. I found only one way to distinguish them: if I need to extend a placeholder I passing only its name. Otherwise I pass another list containing the name of the mixin and its arguments. For example:

@mixin border-solid($width, $color) {
    border: solid #{$width} #{$color};
}
...
.footer, .header, .login-box {
    @include add-styles((
        margin-reset,
        padding-reset,
        size-reset,
        (border-solid, 3px, #F90)
    ));
}

So, while going through all the items in the list, I check if the current one is a list. Thankfully there is a function called type_of and it returns the type of the passed variable.

@mixin add-styles($items) {
    @each $item in $items {
        @if type_of($item) == "list" {
            // call a mixin
        } @else {
            @extend %#{$item};
        }
    }
}

As we discussed in the first part of this article, we can’t use interpolation while including a mixin. Code like this is actually wrong:

@include #{$item}();

As far as I know, there is no solution for that, so I came with a simple if-else markup:

@mixin add-styles($items) {
    @each $item in $items {
        @if type_of($item) == "list" {
            @if nth($item, 1) == "border-solid" {
                @include border-solid(nth($item, 2), nth($item, 3));
            }
        } @else {
            @extend %#{$item};
        }
    }
}

I wasn’t happy with this solution mainly because I had to describe every single mixin inside add-style. Nonetheless, I was ready to use it as is, but even this version didn’t work. If I remove the placeholders and send only (border-solid, 3px, #F90) I get:

Syntax error: Invalid CSS after "%": expected placeholder name, was "3px"

This is because if you have a list with only one element it is cast as a string. Thus, my check @if type_of($item) == "list" doesn’t work. It goes to the @else statement and tries to extend a placeholder.

Conclusion

Hopefully in sharing these limitations, I might help some of you who are experimenting with similar solutions in Sass. As I mentioned earlier in the article, some changes that might affect this are under discussion, so who knows what the future might hold.

This article was originally published at https://krasimirtsonev.com/blog/article/Two-handy-and-advanced-SASS-features-and-their-limitations

Previous

Introduction to Animating in HTML

Responsive Content Using CSS Regions

Next