Yesterday we learned about basic Sass features like variables, nesting and some other methods, but sass has a lot more to offer. For example: mixins. In simple terms, we can say that a function has two objectives; to return or to modify a value. Sass uses two separated features for each objective, mixins and functions. Mixins allow us to define reusable blocks of properties. They also accept variables and conditionals to modify code blocks. Functions are similar, but they just return a single value. Each one has its own use case. When you need to generate styles, use a mixin. When you need to encapsulate some logic, use a function. In this video we're going to see how can we improve our workflow using mixins, and we will see functions later on.

We're going to continue the example we started yesterday. Here we have our index.html. Now we open our developer tools to see how our site looks in mobile, and we have a problem. It's not escalating, so let's add our viewport meta.


<meta name="viewport" content="width=device-width, initial-scale=1.0">

Now we save this. Now the layout is adapting to mobile correctly. It looks bad, so we need to add a responsive style for our site. This is a perfect opportunity to show off sass, because we all know how annoying media queries can be.

What we're going to do is open our _mixins.scss file and define some mixins, but before that, let's use plain css and then we'll migrate that to sass.


.default-button {
  margin: .5rem auto;
}

@media (min-width: 30rem) {
  .default-button {
    margin: 0 .5rem;
  }
}

Here we just define a margin top and bottom for default-button in screens with width superior to 30rem (480px). You need four lines of code just to define something as simple as a margin. As projects grow larger, the above approach of writing media queries usually leads to a lot of duplicate code or splitting the code by viewport sizes. Either way, it’s time consuming and a pain to go jumping from place to place just to update a single element. However, using Sass mixins we can do media queries a lot cleaner.

Remember we can use variables now, and Sass allows us to interpolate them. So, we can define the sizes in variables like this:


$sm: 30rem;
$md: 48rem;

To define our mixin we use @mixin followed by the name we want it to have. I recommend using a short and semantic name: I'll use 'mobile' and 'tablet'. Now we use @media just like before. However, instead of hardcoding the size, we pass our variable and use a directive called @content inside of the curly braces. This allows us to pass content blocks inside our mixin.


@mixin mobile{
  @media (min-width: #{$sm}) {
    @content;
  }
}

@mixin tablet{
  @media (min-width: #{$md}) {
    @content;
  }
}

Now we can go to our default-button file and use it like this:


.default-button {
  ...
  @include mobile {
    margin: 0 .5rem;
  }
  ...
}

It's easier to maintain because we just have to look in the file where our element is defined; it’s also a lot more dry. However, with this approach if we need 4-5 breakpoints, we have to define 4-5 mixins as well. That's not our objective. So, let's automate that even more. Variables in Sass are not limited to numbers or strings. We can define a map which will contain our breakpoints. So, let's do it.


$breakpoints: (
  "xs": 30rem,
  "sm": 35.5rem,
  "md": 48rem,
  "lg": 64rem,
  "xl": 80rem
);

Now that we have our map, we're going to define our mixin. I'll call it media. It will receive two parameters. The breakpoint, which represents the screen width; and the type, which refers to max and min. We want our mixin to be useful for any approach. I prefer mobile first, so I'll define min as the default type.


@mixin media($breakpoint, $type: min) {
}

We need to iterate through all values defined in $breakpoints, and use them. This is trickier than it sounds. What if the breakpoint we pass to the mixin doesn't exist in our map? How can we get the correct value from the map without hardcoding it? Luckily there are various ways to do it. We can use conditionals and loops in Sass, and also we have a lot of useful methods, which you can see in the offical Sass documentation. I'll use map_has_key, which is one of those Sass methods. What it does is check whether a map has a value associated with a given key, basically by receiving two parameters, the array and the key. Conditionals give us the possibility to set different scenarios. So, we can use @if combined with the above method to see if the breakpoint we're passing to the mixin exists. Let’s do it:


@mixin media($breakpoint, $type: min) {
  @if map_has_key($breakpoints, $breakpoint) {
  }
}

So map_has_key will go through the array looking for a matching key; if the key is found that means the breakpoint exists, so we can proceed. Now we need to get the correct value associated with each breakpoint. For that we can use another Sass method: map_get, which returns the value in a map associated with a given key. This method receives the same parameters as map_has_key and we can use it to assign the value we need to a variable. Then, we just use that variable like we did before.

So what we're going to do is define a variable called $width which is equal to map_get(our_breakpoint_array, desired_breakpoint),


@mixin media($breakpoint, $type: min) {
  @if map_has_key($breakpoints, $breakpoint) {
    $width: map_get($breakpoints, $breakpoint);
  }
}

Then we use another conditional to check the type of media query (min or max). If it's max, we subtract 0.1rem from the breakpoint. If we don't, then screens that have the same width as the breakpoint will have issues. Lastly, we interpolate the variables just like before, and use @content.


@mixin media($breakpoint, $type: min) {
  @if map_has_key($breakpoints, $breakpoint) {
    $width: map_get($breakpoints, $breakpoint);
    @if $type == max {
      $width: $width - .1rem;
    }
    @media only screen and (#{$type}-width: $width) {
      @content;
    }
  }
}

And now we can use it like this:


.default-button {
  ...
  @include media('sm’) {
    margin: 0 .5rem;
  }
  ...
}

This is a lot more dry than before. We now have just one mixin that assigns all our media queries. Speaking of dry, look at this mixin: flex_center. This has the same problem we just faced; we should fix it. So we'll do a new mixin using the same approach. First we define a map filled with the variations we want to have. I'll call it $flex_variations


$flex_variations: (
  "default": true,
  "center": true,
  "flex-end": true,
  "flex-start": true
);

We assign true as value to all, just to fill the requirement parameters of map_has_key. Then, we create our mixin. I'll call it flexbox; this will receive two parameters, the variation value (center, flex-end, etc) and the position (vertical, horizontal, or total). Just like before, we verify that our flex-variation exists with map_has_key, and then we define properties according to each variation:


@mixin flexbox($position, $variation: 'default') {
  @if map_has_key($flex_variations, $variation) {
    display: flex;
    flex-wrap: wrap;
    @if $position == 'h' {
      justify-content: unquote($variation);
    }
    @else if $position == 'v' {
      align-content: unquote($variation);
      align-items: unquote($variation);
    }
    @else if $position == 't' {
      justify-content: unquote($variation);
      align-content: unquote($variation);
      align-items: unquote($variation);
    }
  }
}

Notice we use unquote, which is a Sass method that does exactly what the name implies. It removes the quotes from strings, which is required. If we don't, the value will not be recognized as valid, because the flex properties we’re using do not support strings. Now we can use the mixin like this:


.banner-wrapper {
  @include flexbox('t', 'center');
  ...
}

Great! There's a couple more examples I want to show you, but we’re going to see them tomorrow. See ya!