Responsive Font Sizes with Sass Maps and @each
Tags: sass, maps, responsive
Published: 2022-08-13 15:04:00

Hi, there! Today, you will learn how to create responsive font sizes with Sass maps and `@each` method.

I hope you enjoy it!

Let's start with a map and we can dive into more complex concepts with `@each` operation.

First of all, create a map that contains font sizes for 2 different screen sizes. Call them `sm` and `lg`, which stand for small and large screen sizes respectively. Both should contain the same key with different values.

> If you don't know what Sass map is, it is equivalent to object in JavaScript or dictionary in Python.

`_type-scale.scss`
```scss
$type-scale: (
  sm: (
    300: 0.875rem,
    400: 1rem,
    500: 1.125rem,
    600: 1.375rem,
    700: 2rem,
  ),
  lg: (
    300: 1rem,
    400: 1.125rem,
    500: 1.375rem,
    600: 2rem,
    700: 2.75rem,
  ),
);
```

Okay, you might probably know that one good way to create responsive font sizes in CSS is with custom properties. And, their value can be easily changed. It looks something like this:

`example.css`
```css
:root {
  --fs-300: 0.875rem;
  --fs-400: 1rem;
  --fs-500: 1.125rem;
  --fs-600: 1.375rem;
  --fs-700: 2rem;
}

@media only screen and (min-width: 50em) {
  :root {
    --fs-300: 1rem;
    --fs-400: 1.125rem;
    --fs-500: 1.375rem;
    --fs-600: 2rem;
    --fs-700: 2.75rem;
  }
}
```

It is easy to change the value of custom properties at any given point. However, with Sass, we have what we call *variables* whose value are quite tough to change. That said, we can use the superpower of Sass, which will help us to generate those custom properties. You might be saying what is the point of making things complicated even though at the end of the day we end up with these custom properties. It's true, but, Sass is like an organized version of CSS and provides features that makes the code more robust and organized.

Okay, let's get back to the topic. Our final goal is to turn the map (in `_type-scale.scss`) into custom properties (in `example.css`). Whenever you see or use Sass maps, you might probably have seen `@each` rule. It is because they work very well together. You can describe them as arrays with `map()` or `forEach` functions in JavaScript.


So, start by creating a `@each` rule. It will take 3 parameters. 

- 1st: key in the map
- 2nd: value in the map
- 3rd: the map itself

> **Note**: You can name key and value whatever you want, but you need to specify which map you are using.

It looks something like this:

`each-example.scss`
```scss
$colors: (
  primary: #06f,
  secondary: #333,
  accent: #f70,
);

@each $key, $value in $colors {
  .text-#{$key} {
    color: $value;
  }
}
```

Compiled CSS:

`each-example.css`
```css
.text-primary {
  color: #06f;
}

.text-secondary {
  color: #333;
}

.text-accent {
  color: #f70;
}
```

> `#{$key}` - this weird looking thing is called _Interpolation_. It is used to inject values into strings. You can learn more about it [here](https://sass-lang.com/documentation/interpolation).

As you can see, Sass is a truly powerful language. If we describe CSS as JavaScript, Sass can be seen as React.

Ok, I hope you got the idea of how `@each` and maps work very well together. Now, let's generate our responsive font sizes.

Create a `@each` rule that loops through `$type` map and generate following output:

`example.css`
```css
:root {
  --fs-300: 0.875rem;
  --fs-400: 1rem;
  --fs-500: 1.125rem;
  --fs-600: 1.375rem;
  --fs-700: 2rem;
}

@media only screen and (min-width: 50em) {
  :root {
    --fs-300: 1rem;
    --fs-400: 1.125rem;
    --fs-500: 1.375rem;
    --fs-600: 2rem;
    --fs-700: 2.75rem;
  }
}
```

> You need to use nested `@each` rule for nested maps! Also, you might need to use `@if` and `@else` rules. You can learn more about them [here](https://sass-lang.com/documentation/at-rules/control/if). Yes, we have if statements in Sass, too.:)

Okay, start with the first layer of `@each` rule and see what it will output.

`style.scss`
```scss
@use 'type-scale' as *;

:root {
  @each $key, $value in $type-scale {
    --#{$key}: $value;
  }
}
```

Compiled CSS:

`style.css`
```css
:root {
  --sm: $value;
  --lg: $value;
}
```

Whenever you see that it returns `$value` in CSS, just interpolate it.

`style.scss`
```scss
@use 'type-scale' as *;

:root {
  @each $key, $value in $type-scale {
    --#{$key}: #{$value};
  }
}
```

Now, out of nowhere you will get an error, saying it is not valid CSS. Becuase `$key` is returning screen sizes (sm, lg), and `$value` is trying to return another map inside those keys. Let's create one more `@each` and see what it will return.

> Dont' forget that `$value` is returning inner map, so the second `@each` should be relative to `$value`.

`style.scss`
```scss
:root {
  @each $screen-size, $sizes in $type-scale {
    @each $key, $value in $sizes {
      --fs-#{$key}: #{$value};
    }
  }
}
```

If you remember, in the first layer of `@each`, `$key` returned screen sizes (sm, lg) and `$value` returned another map, containing all the sizes of each key. So, I called `$key` as `$screen-size` and `$value` as `$sizes`. 

Now, in our inner `@each` rule, we are asking it to return keys and values inside those sizes.

Compiled CSS:

`style.css`
```css
:root {
  --fs-300: 0.875rem;
  --fs-400: 1rem;
  --fs-500: 1.125rem;
  --fs-600: 1.375rem;
  --fs-700: 2rem;
  --fs-300: 1rem;
  --fs-400: 1.125rem;
  --fs-500: 1.375rem;
  --fs-600: 2rem;
  --fs-700: 2.75rem;
}
```

Okay, we are getting there. A few things left to do. Now, we need to find which key belongs to `sm` or `lg` screen sizes. And, here `@if` and `@else` come very handy.

So, `$screen-size` return screen-sizes (sm, lg). If it returns `sm`, just return it inside the `:root`. Else return it inside the media-query.

> You need to use `@if` and `@else` statements after the first layer of `@each` rule, not the second!. If it satisfies, return the second layer of `@each` rule that we wrote above.

`style.scss`
```scss
@use 'type-scale' as *;

:root {
  @each $screen-size, $sizes in $type-scale {
    @if $screen-size == sm {
      @each $key, $value in $sizes {
        --fs-#{$key}: #{$value};
      }
    } @else {
      @media (min-width: 50em) {
        @each $key, $value in $sizes {
          --fs-#{$key}: #{$value};
        }
      }
    }
  }
}
```

Compiled CSS:

`style.css`
```css
:root {
  --fs-300: 0.875rem;
  --fs-400: 1rem;
  --fs-500: 1.125rem;
  --fs-600: 1.375rem;
  --fs-700: 2rem;
}

@media (min-width: 50em) {
  :root {
    --fs-300: 1rem;
    --fs-400: 1.125rem;
    --fs-500: 1.375rem;
    --fs-600: 2rem;
    --fs-700: 2.75rem;
  }
}
```


Yes, we did it! As you might have guessed, we can add more screen sizes. And, it is a task for you if you want to give it a shot.

If you have any doubts, questions or feedback, please do consider sharing it with me. I really appreciate it and get back to you as soon as possible!

If you are new to Sass maps and `@each` method, I highly recommend checking out Kevin Powell's video where he explained the superpower of Sass with custom properties and utility classes. It is where I learned these cool things, too.:)

- [Custom props & utility classes](https://www.youtube.com/watch?v=gP8yFWCTr7Q) by Kevin Powell

---

That's it! I hope you liked it!

You can get the source code from this [GitHub repo](https://github.com/dostonnabotov/frontend-guide/tree/main/sass/type-scale)

If you've liked this article, please do consider sharing it with others. It means the world to me