SASS – mixin for responsive design

In this post, I am going to explain how to use SASS mixin for responsive design which is very powerful way.

In CSS, there is a media query you can use to present a different layout for different screen size. However, it would be very tedious to write media query break points in every CSS component you write. What if screen size changes for a phone or tablet? You probably do not want to go through everything to fix it. There needs to be a better way to handle different screen size flexibly and let’s take a look at each option

mixin directive

what is mixin? It is a directive and lets you create CSS code that is to be reused throughout the website. It’s basically like a function in different programming languages.

Let’s first define each breakpoints. These are the breakpoints I want to be able to handle.

0 - 600px: phone
600 - 900px: Tablet portrait
900 - 1200px: Tablet landscape
[1200-1800] is where our normal styles apply
1800px + : Big Desktop

Use mixin directive – option 1

We are going to create a mixin called respond-phone so that you can just include the mixin in each component/layout for phone.
Please note that @content directive allows to pass in a block of code to mixin.

// _mixins.scss
@mixin respond-phone {
    // content directive allows to pass in a block of code to mixin
    @media (max-width: 600px) { @content };
}

// _base.scss
html {
    font-size: 62.5%;
    
    @include respond-phone {
        font-size: 50%;
    }
}

Originally, the font size is 62.5% of the root font size. However, by using the respond-phone mixin, it changes to 50%. It is pretty convenient because you don’t need to hard code media query every place. Instead, you just need to create necessary mixin functions (i.e., respond-tablet-portrait, respond-tablet-landscape) and use it. Although this is certainly an improvement, is there a better way? Is there a way to use just one mixin to represent all?

Use mixin directive – option 2

Since mixin is like a function, it can take an argument. It enables us to write a mixin that specifies proper media query breakpoint based on the argument!

Please note that unlike the example above, I used em instead of px. Why? It’s because if a user changes the default font size in a browser media queries will not be affected by that. However, we want the media query to reflect the change in default font size in a browser setting. Since em is a relative unit, it is the perfect one to use.

In a media query, em is always affected by the one that comes from the browser which is 16px. 1em = 16px

// _mixins.scss
/*
$breakpoint argument choices:
- phone
- tab-port
- tab-land
- big-desktop
*/
@mixin respond($breakpoint) {
  @if $breakpoint == phone {
    @media (max-width: 37.5em) { @content }; // 600px
  }
  @if $breakpoint == tab-port {
    @media (max-width: 56.25em) { @content }; // 900px
  }
  @if $breakpoint == tab-land {
    @media (max-width: 75em) { @content }; // 1200px
  }
  @if $breakpoint == big-desktop {
    @media (min-width: 112.5em) { @content }; // 1800px
  }
}

Now, we have a single mixin to use for all different screen sizes. Let’s take a look at how to use them

// _base.scss
html {
  font-size: 62.5%; // 1rem = 10px; 10 / 16 = 62.5%

  @include respond(tab-land) { // width < 900?
    font-size: 56.25%; // 1rem = 9px, 9 / 16 = 56.25%
  }

  @include respond(tab-port) { // width < 600 ?
    font-size: 50%; // 1rem = 8px, 8 / 16 = 50%
  }

  @include respond(phone) {
    font-size: 43.75%; // 1rem = 7px, 7 / 16 = 43.75%
  }

  @include respond(big-desktop) {
    font-size: 75%; // 1rem = 12px, 12/16 = 75%;
  }
}

Here, we are trying to use font-size as a breakpoint for different screen layout. You can see that I want to use different font-size for different screen and I converted to percentage to be flexible.

Please note the order of the mixin. All the max-width mixins are in a decreasing order. It’s ordered to prevent unintentional side effect. For example, if the screen size is 500px, it will fall under all the tablets and phone size. In that case, the last property written is selected and that’s why I ordered them in a decreasing size way.

Conclusion

It’s not simple to write a responsive designs due to its complexity. However, using the mixin could help a lot.

CSS – Build Animating Nav Button

Today, we are going to take a look at how to build animating nav button using css properties.
Please note that I have learned this from the udemy course by Jonas Schmedtmann.

Example

As soon as you click the button, you will see there is a cool animation making the button X. This is built by pure CSS properties which we will take a look at it soon.

HTML Code

<div class="navigation">
    <input type="checkbox" class="navigation__checkbox" id="navi-toggle">
    <label for="navi-toggle" class="navigation__button">
        <span class="navigation__icon"> </span>
    </label>
</div>

checkbox input type is used here to make toggle easier. Please note that the checkbox input is hidden and the navigation__icon class is used to style like a button.

CSS Code – SCSS

Please don’t forget to change the SCSS variables to actual numbers when you use it.

.navigation {
  &__button {
    background-color: $color-white;
    height: 7rem;
    width: 7rem;
    border-radius: 50%;
    position: fixed;
    top: 6rem;
    right: 6rem;
    z-index: 1000;
    box-shadow: 0 1rem 3rem rgba($color-black, .1);
    text-align: center;
    cursor: pointer;
  }    
    
  &__icon {
    position: relative;
    margin-top: 3.5rem;;

    &,
    &::before,
    &::after {
      width: 3rem;
      height: 2px;
      background-color: $color-gray-dark-3;
      display: inline-block;
    }

    &::before,
    &::after {
      content: "";
      position: absolute;
      left: 0;
      transition: all .2s;
    }

    &::before { top: -.8rem; }
    &::after { top: .8rem; }
  }

  &__button:hover &__icon:before {
    top: -1rem;
  }
  &__button:hover &__icon:after {
    top: 1rem;
  }

  &__checkbox:checked + &__button &__icon {
    // make the middle one invisible
    background-color: transparent;
  }

  &__checkbox:checked + &__button &__icon::before {
    top: 0;
    transform: rotate(135deg);
  }

  &__checkbox:checked + &__button &__icon::after {
    top: 0;
    transform: rotate(-135deg);
  }
}

I skipped navigation class here as it’s explained in different post. Please refer to the post here for navigation logic.

As you can see in the html code, there is only 1 icon element in the button. How can there be three lines? Take a look at the line 20-27. 3 selectors are chosen – before, the element, after – and each selector make a line in the button. Please note that content, display properties are mandatory for pseudo-element usage.

Now, let’s see how you can make the animation when hover. There is a couple of steps for this.

Make a space between the lines when hover

Take a look at lines 41 – 46 in the CSS code. You need to choose before, after when hover happens and adjust the position accordingly. Don’t forget to have transition property to make it animation in line 34.

Animating the lines to form X

Take a look at lines 48 – 61 in the CSS code. What you need to do is the followings.
1. hide the middle one (background-color: transparent)
2. re-position the top and bottom (before, after pseudo elements) and rotate in opposite directions. Here 135 degree is chosen (180 – 45)

The logic is pretty simple. We just need to carefully select the element – only when the checkbox is checked. In order to do that, sibling selector (+) is used then choose navigation__icon::before and after

CSS SASS float grid explained

There is a couple of ways to implement CSS grid. Although float grid is not the most recommended method I still wanted to do that for academic purpose.

In this example, I am going to implement a CSS grid like the image below. There will be 6 rows total and each row will have its own columns.

HTML Code

Here is the HTML code showing how each row/columns are respresented. There will be 6 css classes to represent each column.
1. col-1-of-2: column size 1 out of 2 (50%)
2. col-1-of-3: column size 1 out of 3 (33%)
3. col-1-of-4: column size 1 out of 4 (25%)
4. col-2-of-3: column size 2 out of 3 (66%)
5. col-2-of-4: column size 2 out of 4 (50%)
6. col-3-of-4: column size 3 out of 4 (75%)

<section class="grid-test">
    <div class="row">
        <div class="col-1-of-2">
            Col 1 of 2
        </div>
        <div class="col-1-of-2">
            Col 1 of 2
        </div>
    </div>
    
    <div class="row">
        <div class="col-1-of-3">
            Col 1 of 3
        </div>
        <div class="col-1-of-3">
            Col 1 of 3
        </div>
        <div class="col-1-of-3">
            Col 1 of 3
        </div>
    </div>
    
    <div class="row">
        <div class="col-1-of-3">
            Col 1 of 3
        </div>
        <div class="col-2-of-3">
            Col 2 of 3
        </div>
    </div>
    
    <div class="row">
        <div class="col-1-of-4">
            Col 1 of 4
        </div>
        <div class="col-1-of-4">
            Col 1 of 4
        </div>
        <div class="col-1-of-4">
            Col 1 of 4
        </div>
        <div class="col-1-of-4">
            Col 1 of 4
        </div>
    </div>
    
    <div class="row">
        <div class="col-1-of-4">
            Col 1 of 4
        </div>
        <div class="col-1-of-4">
            Col 1 of 4
        </div>
        <div class="col-2-of-4">
            Col 2 of 4
        </div>
    </div>
    
    <div class="row">
        <div class="col-1-of-4">
            Col 1 of 4
        </div>
        <div class="col-3-of-4">
            Col 3 of 4
        </div>
    </div>
</section>

CSS (SASS) Code

There is a couple of things to note.

  • :not(:last-child) means select everything except the last child because we don’t want the last child to have margin bottom.
  • attribute selector [] in line 27. Note that there is a common code for all the column classes and we don’t want to repeat that. attribute selector is used in order to solve duplicate code. [class^=”col-“] means to select all the elements with the class attributes starts with ‘col-‘ string. It also implies that you can use any attributes to select elements.
  • calc is a provided function that can calculate an equation. It is able to accept % in the equation.
$grid-width: 114rem;
$gutter-vertical: 8rem;
$gutter-horizontal: 6rem;

@mixin clearfix {
  &::after {
    content:  "";
    display: table;
    clear: both;
  }
}

.row {
  max-width: $grid-width;
  background-color: #eee;
  margin: 0 auto;

  &:not(:last-child) {
    margin-bottom: $gutter-vertical;
  }

  @include clearfix;
  // select all the elements with the class attributes starts with 'col-'
  // ^ start with
  // * contain
  // $ end with
  [class^="col-"] {
    color: white;
    height: 5rem;
    font-size: 3rem;
    background-color: orangered;
    float: left;

    &:not(:last-child) {
      margin-right: $gutter-horizontal;
    }
  }

  .col-1-of-2 {
    width: calc((100% - #{$gutter-horizontal}) / 2);
  }

  .col-1-of-3 {
    width: calc((100% - 2 * #{$gutter-horizontal}) / 3);
  }

  .col-1-of-4 {
    width: calc((100% - 3 * #{$gutter-horizontal}) / 4);
  }

  .col-2-of-3 {
    width: calc(2 * ((100% - 2 * #{$gutter-horizontal}) / 3) + #{$gutter-horizontal});
  }

  .col-2-of-4 {
    width: calc(2 * ((100% - 3 * #{$gutter-horizontal}) / 4) + #{$gutter-horizontal});
  }

  .col-3-of-4 {
    width: calc(3 * ((100% - 3 * #{$gutter-horizontal}) / 4) + 2 * #{$gutter-horizontal});
  }
}

How each equation is decided

  • col-1-of-2
    Check the first row. You only have 2 columns and 1 gutter. The width of pure columns is (100% – gutter). Then since there are two columns in the row you need to divide it by 2. The width of a single column is then (100% – gutter) / 2
  • col-1-of-3
    Check the second row. You have 3 columns and 2 gutters. The width of pure columns is (100% – 2 * gutter). Then since there are three columns in the row you need to divide it by 3. The width of a single column is then (100% – 2 * gutter) / 3
  • col-2-of-3
    This one seems to be a bit tricky but it’s actually pretty simple. If you look at row 2, 2-of-3 is actually the sum of two col-1-of-3 and one gutter.
    The width of a single column is then (2 * 2-of-3 + gutter)
  • col-3-of-4
    This is the exact same case as the above. This is the sum of three col-1-of-4 and two gutters.
    The width of a single column is then (3 * 1-of-4 + 2 * gutter)