Preface

Goal: Generate custom spacing classes step by step, utilizing postcss loop capability.

Even when utilizing CSS Framework, theme making require to create custom CSS. One of my need is spacing class, that is not available in neither Bulma nor Materialize. This spacing class can be made utilizing PostCSS, or to be exact, using PreCSS plugin.

PostCSS: script

These guidance will save you a lot of coding time. And also tons of typing.

Reading

All code below are ported from the previous SASS article:


Configuration

This assume you have read the previous articles. We are using this configuration:

module.exports = {
  syntax: 'postcss-scss',
  plugins: [
    require('postcss-strip-inline-comments'),
    require('postcss-each'),
    require('precss'),
    require('postcss-prettify'),
  ],
}

If you haven’t read the configuration guidance yet, I suggest you to take one step behind to this article:


0: Comment

Consider test our postcss with gulp to remove this simple comment.

PostCSS Source

// This line will be removed

.m-1 {
  margin: 1px;
}

CSS Result

The result would be:

.m-1 {
  margin: 1px;
}

1: @for

Imagine that we need a sequence of classes to handle margin, similar like CSS below:

.m-1 {
  margin: 1px;
}

This can be achieved with this loop constructor

@for ... from ... to ... {
  ...
}

CSS Class Naming

We want to achieve this class naming

  • .m-$(number)

PostCSS Source

For comfort reason, we also need to define intial value for the loop.

// variable initialization

$loop-begin: 1;
$loop-stop:  3;

// loop

@for $cursor from $loop-begin to $loop-stop {
  .m-$(cursor) {
    margin: $(cursor)px;
  }
}

Variable Interpolation

Inside the loop we have this $(…) code. This is variable interpolation.

We use variable interpolation, when we want to extract value into the css.

CSS Result

The #{$cursor} variable takes care the rest, so now we have this result below:

.m-1 {
  margin: 1px;
}
.m-2 {
  margin: 2px;
}
.m-3 {
  margin: 3px;
}

Limitation

The issue with @for is we cannot use interval. We can have this [1, 2, 3, 4, 5] sequence. But we cannot have this [5, 10, 15, 20, 25] sequence.


2: @for by Interval

To solve [5, 10, 15, 20, 25] sequence, we can utilize @while.

@for ... from ... to ... by ... {
  ...
}

CSS Class Naming

We still want to achieve this class naming

  • .m-$(number)

PostCSS Source

Consider this @for loop below:

// variable initialization

$loop-begin: 5;
$loop-stop: 25;
$interval:   5;

// loop

@for $cursor from $loop-begin
to $loop-stop by $interval {
  .m-$(cursor) {
    margin: $(cursor)px;
  }
}

CSS Result

.m-5 {
  margin: 5px;
}
.m-10 {
  margin: 10px;
}
.m-15 {
  margin: 15px;
}
.m-20 {
  margin: 20px;
}
.m-25 {
  margin: 25px;
}

3: @each

Before facing complex situation, consider a simple example.

Challenge

Now we have challenge that margin property has these variants:

  • margin,

  • margin-top,

  • margin-bottom,

  • margin-left,

  • margin-right

For simplicity reason, I exclude the original margin variant.

CSS Class Naming

We want to achieve this class naming

  • .m-$(side)-5

List Declaration

PostCSS can handle a list.

(top, bottom, left, right)

Accessing List

This list can be accessed by using @each iterator.

@each ... in ... {
  ...
}

PostCSS Source

Consider accessing $sides by this example below:

@each $side in (top, bottom, left, right) {
  .m-$(side)-5 {
    margin-$(side): 5px;
  }
}

CSS Result

Now we have this:

.m-top-5 {
  margin-top: 5px;
}
.m-bottom-5 {
  margin-bottom: 5px;
}
.m-left-5 {
  margin-left: 5px;
}
.m-right-5 {
  margin-right: 5px;
}

This is ugly, because we want .m-t-5, instead of .m-top-5.


4: @each with Pairs

Sass has solution for this issue.

CSS Class Naming

We want to achieve this class naming

  • .m-$(abbreviation)-5

List Declaration

Luckily, PostCSS allow us to have pairs in list. Now we have property as pairs as shown below:

(top, bottom, left, right), (t, b, l, r)

PostCSS Source

Consider rewrite previous code.

// sides : properties, abbreviation;

@each $side, $name 
in (top, bottom, left, right), (t, b, l, r) {
  .m-$(name)-5 {
    margin-$(side): 5px;
  }
}

CSS Result

Now we have what wee need.

.m-t-5 {
  margin-top: 5px;
}
.m-b-5 {
  margin-bottom: 5px;
}
.m-l-5 {
  margin-left: 5px;
}
.m-r-5 {
  margin-right: 5px;
}

Notice that I use .m-t-5, instead of .mt-5, to differ from bootstrap spacing classes.


5: @for @each

Consider leverage to a more complex situation. We need a sequence of each classes.

CSS Class Naming

We want to achieve this class naming

  • .m-$(subname)-$($number)

Skeleton

We put @each iterator inside @for loop.

@for $cursor from $loop-begin
to $loop-stop by $interval {

  @each $side, $name
  in (top, bottom, left, right), (t, b, l, r) {
    ...
  }
}

PostCSS Source

// variable initialization

$loop-begin: 5;
$loop-stop: 25;
$interval:   5;

@for $cursor from $loop-begin
to $loop-stop by $interval {
  
  @each $side, $name
  // sides: properties, abbreviation;
  in (top, bottom, left, right), (t, b, l, r) {
    .m-$(name)-$(cursor) {
      margin-$(side): $(cursor)px;
    }
  }
}

CSS Result

.m-t-5 {
  margin-top: 5px;
}
.m-b-5 {
  margin-bottom: 5px;
}
.m-l-5 {
  margin-left: 5px;
}
.m-r-5 {
  margin-right: 5px;
}
.m-t-10 {
  margin-top: 10px;
}
.m-b-10 {
  margin-bottom: 10px;
}
.m-l-10 {
  margin-left: 10px;
}
.m-r-10 {
  margin-right: 10px;
}
.m-t-15 {
  margin-top: 15px;
}
.m-b-15 {
  margin-bottom: 15px;
}
.m-l-15 {
  margin-left: 15px;
}
.m-r-15 {
  margin-right: 15px;
}
.m-t-20 {
  margin-top: 20px;
}
.m-b-20 {
  margin-bottom: 20px;
}
.m-l-20 {
  margin-left: 20px;
}
.m-r-20 {
  margin-right: 20px;
}
.m-t-25 {
  margin-top: 25px;
}
.m-b-25 {
  margin-bottom: 25px;
}
.m-l-25 {
  margin-left: 25px;
}
.m-r-25 {
  margin-right: 25px;
}

6: Nested @for

CSS Class Naming

We want to achieve this class naming

  • .$(name)-$(subname)-$(number)

List Declaration

We define CSS property list:

  • property: margin and padding
(margin, padding), (m, p)
  • each has sub-property: top, bottom, left, right.
(top, bottom, left, right), (t, b, l, r)

Skeleton

We put both @each iterator, inside @for loop.

@for $cursor from $loop-begin
to $loop-stop by $interval {

  @each $prop, $name
  in (margin, padding), (m, p) {  
    @each $side, $subname
    in (top, bottom, left, right), (t, b, l, r) {
      ...
    }
  }
}

PostCSS Source

// variable initialization

$loop-begin: 5;
$loop-stop: 25;
$interval:   5;

@for $cursor from $loop-begin
to $loop-stop by $interval {

  @each $prop, $name
  in (margin, padding), (m, p) {  
    @each $side, $subname
    in (top, bottom, left, right), (t, b, l, r) {
      .$(name)-$(subname)-$(cursor) {
        $(prop)-$(side): $(cursor)px;
      }
    }
  }
}

CSS Result

Now we have both margin and padding.

.m-t-5 {
  margin-top: 5px;
}
.m-b-5 {
  margin-bottom: 5px;
}
.m-l-5 {
  margin-left: 5px;
}
.m-r-5 {
  margin-right: 5px;
}
.p-t-5 {
  padding-top: 5px;
}
.p-b-5 {
  padding-bottom: 5px;
}
.p-l-5 {
  padding-left: 5px;
}
.p-r-5 {
  padding-right: 5px;
}
.m-t-10 {
  margin-top: 10px;
}
.m-b-10 {
  margin-bottom: 10px;
}
.m-l-10 {
  margin-left: 10px;
}
.m-r-10 {
  margin-right: 10px;
}
.p-t-10 {
  padding-top: 10px;
}
.p-b-10 {
  padding-bottom: 10px;
}
.p-l-10 {
  padding-left: 10px;
}
.p-r-10 {
  padding-right: 10px;
}
.m-t-15 {
  margin-top: 15px;
}
.m-b-15 {
  margin-bottom: 15px;
}
.m-l-15 {
  margin-left: 15px;
}
.m-r-15 {
  margin-right: 15px;
}
.p-t-15 {
  padding-top: 15px;
}
.p-b-15 {
  padding-bottom: 15px;
}
.p-l-15 {
  padding-left: 15px;
}
.p-r-15 {
  padding-right: 15px;
}
.m-t-20 {
  margin-top: 20px;
}
.m-b-20 {
  margin-bottom: 20px;
}
.m-l-20 {
  margin-left: 20px;
}
.m-r-20 {
  margin-right: 20px;
}
.p-t-20 {
  padding-top: 20px;
}
.p-b-20 {
  padding-bottom: 20px;
}
.p-l-20 {
  padding-left: 20px;
}
.p-r-20 {
  padding-right: 20px;
}
.m-t-25 {
  margin-top: 25px;
}
.m-b-25 {
  margin-bottom: 25px;
}
.m-l-25 {
  margin-left: 25px;
}
.m-r-25 {
  margin-right: 25px;
}
.p-t-25 {
  padding-top: 25px;
}
.p-b-25 {
  padding-bottom: 25px;
}
.p-l-25 {
  padding-left: 25px;
}
.p-r-25 {
  padding-right: 25px;
}

7: Final

Now comes the final result.

CSS Class Naming

We want to achieve both class naming

  • .$(name)-$(number)

  • .$(name)-$(subname)-$(number)

Skeleton

We put both @each iterator, inside @for loop.

@for $cursor from $loop-begin
to $loop-stop by $interval {

  @each $prop, $name
  in (margin, padding), (m, p) {  
    ...

    @each $side, $subname
    in (top, bottom, left, right), (t, b, l, r) {
      ...
    }
  }
}

PostCSS Source

// variable initialization

$loop-begin: 5;
$loop-stop: 25;
$interval:   5;

@for $cursor from $loop-begin
to $loop-stop by $interval {

  @each $prop, $name
  in (margin, padding), (m, p) {  
    .$(name)-$(cursor) {
      $(prop): $(cursor)px !important;
    }

    @each $side, $subname
    in (top, bottom, left, right), (t, b, l, r) {
      .$(name)-$(subname)-$(cursor) {
        $(prop)-$(side): $(cursor)px !important;
      }
    }
  }
}

Notice that I also put !important in css value.

CSS Result

.m-5 {
  margin: 5px !important;
}
.m-t-5 {
  margin-top: 5px !important;
}
.m-b-5 {
  margin-bottom: 5px !important;
}
.m-l-5 {
  margin-left: 5px !important;
}
.m-r-5 {
  margin-right: 5px !important;
}
.p-5 {
  padding: 5px !important;
}
.p-t-5 {
  padding-top: 5px !important;
}
.p-b-5 {
  padding-bottom: 5px !important;
}
.p-l-5 {
  padding-left: 5px !important;
}
.p-r-5 {
  padding-right: 5px !important;
}
.m-10 {
  margin: 10px !important;
}
.m-t-10 {
  margin-top: 10px !important;
}
.m-b-10 {
  margin-bottom: 10px !important;
}
.m-l-10 {
  margin-left: 10px !important;
}
.m-r-10 {
  margin-right: 10px !important;
}
.p-10 {
  padding: 10px !important;
}
.p-t-10 {
  padding-top: 10px !important;
}
.p-b-10 {
  padding-bottom: 10px !important;
}
.p-l-10 {
  padding-left: 10px !important;
}
.p-r-10 {
  padding-right: 10px !important;
}
.m-15 {
  margin: 15px !important;
}
.m-t-15 {
  margin-top: 15px !important;
}
.m-b-15 {
  margin-bottom: 15px !important;
}
.m-l-15 {
  margin-left: 15px !important;
}
.m-r-15 {
  margin-right: 15px !important;
}
.p-15 {
  padding: 15px !important;
}
.p-t-15 {
  padding-top: 15px !important;
}
.p-b-15 {
  padding-bottom: 15px !important;
}
.p-l-15 {
  padding-left: 15px !important;
}
.p-r-15 {
  padding-right: 15px !important;
}
.m-20 {
  margin: 20px !important;
}
.m-t-20 {
  margin-top: 20px !important;
}
.m-b-20 {
  margin-bottom: 20px !important;
}
.m-l-20 {
  margin-left: 20px !important;
}
.m-r-20 {
  margin-right: 20px !important;
}
.p-20 {
  padding: 20px !important;
}
.p-t-20 {
  padding-top: 20px !important;
}
.p-b-20 {
  padding-bottom: 20px !important;
}
.p-l-20 {
  padding-left: 20px !important;
}
.p-r-20 {
  padding-right: 20px !important;
}
.m-25 {
  margin: 25px !important;
}
.m-t-25 {
  margin-top: 25px !important;
}
.m-b-25 {
  margin-bottom: 25px !important;
}
.m-l-25 {
  margin-left: 25px !important;
}
.m-r-25 {
  margin-right: 25px !important;
}
.p-25 {
  padding: 25px !important;
}
.p-t-25 {
  padding-top: 25px !important;
}
.p-b-25 {
  padding-bottom: 25px !important;
}
.p-l-25 {
  padding-left: 25px !important;
}
.p-r-25 {
  padding-right: 25px !important;
}

Now you can use this spacing sass in your project.


8: Update: XY

For practical reason, I have to update this article.

  • Start form zero .0, instead of 5.

  • Consider X, and Y just like bootstrap.

.m-y-5 {
  margin-top: 5px !important;
  margin-bottom: 5px !important;
}

.m-x-5 {
  margin-left: 5px !important;
  margin-right: 5px !important;
}

The drawback is, I have bigger stylesheet size.

List Declaration

Luckily, sass allow us to have pairs in list. Now we have CSS sub-property as below:

(margin, padding), (m, p)

and

(top, bottom, left, right), (t, b, l, r)

and both below

(top, bottom)
(left, right)

And the loop is a little bit different:

@for $cursor from $loop-begin
to $loop-stop by $interval {
  @each $prop, $name
  in (margin, padding), (m, p) { 
    ...

    @each $side, $subname
    in (top, bottom, left, right), (t, b, l, r) {
      ...
    }
    .$(name)-y-$(cursor) {
      @each $side in (top, bottom) {
        ...
      }
    }
    .$(name)-x-$(cursor) {
      @each $side in (left, right) {
        ...
      }
    }
  }

PostCSS Source

// variable initialization

$loop-begin: 0;
$loop-stop: 25;
$interval:   5;

@for $cursor from $loop-begin
to $loop-stop by $interval {

  @each $prop, $name
  in (margin, padding), (m, p) {  
    .$(name)-$(cursor) {
      $(prop): $(cursor)px !important;
    }

    @each $side, $subname
    in (top, bottom, left, right), (t, b, l, r) {
      .$(name)-$(subname)-$(cursor) {
        $(prop)-$(side): $(cursor)px !important;
      }
    }
    .$(name)-y-$(cursor) {
      @each $side in (top, bottom) {
        $(prop)-$(side): $(cursor)px !important;
      }
    }
    .$(name)-x-$(cursor) {
      @each $side in (left, right) {
        $(prop)-$(side): $(cursor)px !important;
      }
    }

  }
}

CSS Result

These spacing classes is ready to serve.

PostCSS: output


What is Next ?

After this configuration, it is a good time to examine postcss in action. Consider continue reading [ PostCSS - Loop - SugarSS ].

Thank you for reading.