Adam Johnston

Propa Sass

I’ll readily admit I’ve been a bit out of the loop. I recently started a new job so I’m working with new people, learning new practices, and well, new Sass. Sass I hadn’t had the opportunity to use before, in this case it’s Sass maps.

I won’t go on about Sass maps, they’ve been around since Sass 3.3 (7th March 2014) and I am sure me raving about their usefulness is not going to be of much interest. What I did want to write about, which I hope, is going to be interesting is a little experiment into how far I could take Sass maps and what I learnt from doing so.

Let the Experiment Begin!

Using a preprocessor is all about simplification. “How can I get the most done for as little effort?”. That’s why we now have loops, functions, mixins, nesting and variables in our CSS now (there’s plenty more but you get the idea). Maps are just one more tool in the Sass shed. Take this button example below:

.btn {
  width: 150px;
  height: 40px;
  background: grey;
  border: 1px solid darken(grey, 10%);
  color: white;
  display: inline-block;
  vertical-align: bottom;

  &:focus {
    outline: none;
  }

  &:hover,
  &:focus {
    height: 38px;
    margin-top: 2px;
    background: darken(grey, 10%);
  }

  &:active {
    height: 36px;
    margin-top: 4px;
  }

  &--primary {
    background: blue;
    border-color: darken(blue, 10%);

    &:hover,
    &:focus {
      background: darken(blue, 10%);
    }
  }

  &--warning {
    background: red;
    border-color: darken(red, 10%);

    &:hover,
    &:focus {
      background: darken(red, 10%);
    }
  }

  &.is-active {
    animation: glow infinite 3s;
  }
}
chars : 667 | words : 75 | lines: 49

PropaSass Button example 01

We can use Sass maps and lists here to DRY things up a little and make our button class a little more maintainable.

$states: (
  hover: (
    38px,
    2px,
  ),
  focus: (
    38px,
    2px,
  ),
  active: (
    36px,
    4px,
  ),
);

$modifiers: (
  primary: blue,
  warning: red,
);

.btn {
  width: 150px;
  height: 40px;
  background: grey;
  border: 1px solid darken(grey, 10%);
  color: white;
  display: inline-block;
  vertical-align: bottom;

  &:focus {
    outline: none;
  }

  &:hover,
  &:focus {
    background: darken(grey, 10%);
  }

  @each $state, $styles in $states {
    &:#{$state} {
      height: nth($styles, 1);
      margin-top: nth($styles, 2);
    }
  }

  @each $modifier, $colour in $modifiers {
    &--#{$modifier} {
      background: $colour;
      border-color: darken($colour, 10%);

      &:hover,
      &:focus {
        background: darken($colour, 10%);
      }
    }
  }

  &.is-active {
    animation: glow infinite 3s;
  }
}
chars : 757 (+90) | words : 85 (+10) | lines: 49 (N/A)

PropaSass Button example 02

We’ve added a few characters and some words but I believe there are some benefits. In my opinion we have abstracted out the modifiers to the top of our class so from a basic overview we can see how our button styles will change on state and how our button is being modified.

Changing state or adding new modifiers should be easy peasy and won’t require us to make changes to our button class. But what if we took this just a little further?


It’s Alive, IT’S ALIVE!

I think perhaps it’s best to show an early idea of what I am choosing to call PropaSass (The idea of passing ‘props’ is taken from React and a play on words of proper or as some fellow East Londoners may pronounce it, ‘propa’.)

We need to define a function and a couple of mixins which should be added with your other functions and mixins. These handle looping and conversion.

@function class-name($prefix, $modifier) {
  @if not $prefix {
    $prefix: '';
  }

  @if not $modifier {
    $modifier: '';
  }

  @return $prefix + $modifier;
}

@mixin style-with($styles) {
  @if not $props {
    @warn 'No styles were defined';
  }

  @each $style in $styles {
    #{nth($style, 1)}: nth($style, 2);
  }
}

@mixin modify-with($props) {
  @if not $props {
    @warn 'No props were defined';
  }

  @each $prefix, $modifiers in $props {
    @each $modifier, $styles in $modifiers {
      &#{class-name($prefix, $modifier)} {
        @include style-with($styles);
      }
    }
  }
}

So once we have these helpers defined, let’s re-write our button class and put those helpers to work!

$props: (
  &: (
    ':hover, :focus': (
      height: 38px,
      margin-top: 2px,
      background: darken(grey, 10%),
    ),
    ':focus': (
      outline: none,
    ),
    ':active': (
      height: 36px,
      margin-top: 4px,
    ),
  ),
  '--': (
    'primary': (
      background: blue,
      border-color: darken(blue, 10%),
    ),
    'primary:hover, primary:focus': (
      background: darken(blue, 10%),
    ),
    'warning': (
      background: red,
      border-color: darken(red, 10%),
    ),
    'warning:hover, warning:focus': (
      background: darken(red, 10%),
    ),
  ),
  '.is-': (
    'active': (
      animation: glow infinite 3s,
    ),
  ),
);

.btn {
  width: 150px;
  height: 40px;
  background: grey;
  border: 1px solid darken(grey, 10%);
  color: white;
  display: inline-block;
  vertical-align: bottom;

  @include modify-with($props);
}
chars : 787 (+120) | words : 84 (+9) | lines: 44 (-5)

PropaSass button example 03

I want to point out that I am not suggesting this is a good way to write Sass, or that this should even be used but I quite like it and I think it has benefits. We define our base styles and then define our modifier sass map called props, passing them to our new modify-with mixin. We now have a clear separation of how our props will change our button class’ styling. We also avoid any confusing nesting.

Making changes are a little easier and if we want to change how our base style is modified, let’s say for example you really dislike BEM naming (c’mon double hyphen isn’t so bad) you can make that change easily.

One arguable drawback is now though we are defining CSS properties with a weird, Frankenstein’s monster, JSON-like syntax written using a Sass map. (Sass Map Notation? SAMN? Salmon syntax? Whatever, I kinda like it but then I am the mad scientist that created it.)


A less verbose look in an editor: chars : 719 | words : 86| lines: 28 |

Summary

I never set out to create anything along the lines of what you see above (although if you think it’ll be useful, please let me know). I saw a new feature (to me at least) and I wanted to see how far I could take it.

I think this little exercise has given me insights in to how effective the use of maps can be and how they can DRY up Sass. I’d also recommend this kind of experimentation when you find a new feature as there were plenty of problems that needed solving and meant gaining knowledge of new built in SassScript functions that I hadn’t used befor such as map-get and nth.

If you’d like to see the code, I’ve created a Github repo and if you’ve liked what you’ve seen and read, feel free to contribute and improve PropaSass.