Design

Design

Five Compass & Sass Workflow Tips

This is an intermediate article on Compass & Sass. Sass is a Ruby meta-language on top of CSS that makes CSS more like a programming language and less like a static markup language. Compass is a library and utility that leverages Sass with @mixin functions. @mixin functions do a lot of calculations and browser-specific prefixes for you.

Since Compass and Sass were released (and other similar tools), Web design has changed for the better. Compass and Sass have made mockup-less workflows entirely possible, especially when combined with tools like Susy. I’ve all but dropped traditional desktop software as design tools. I perform my design work in the browser.

We’ll explore five tips that build on each other as we design a simple dashboard. If you'd like, you may view the source files on Git.

We have basic styles for the layout and underlying topography for the dashboard. This will be our starting point:

Simple dashboard wireframe

Tip One: Build on the @mixin

The first step of designing this wireframe will be to create hierarchy and set groupings. We have several elements on this page that will receive a similar aesthetic. There is a “load more” button at the bottom of the recent activity feed, anchors beneath the user’s account and a “log out” anchor at the top. To the user, these items represent actions they can perform. That means the elements should have a similar look and feel, which indicates they are interactive. Not all of these elements will look exactly the same, and we may want to carry portions of them over to other elements.

Let’s start by breaking our main sections apart visually. We'll do this by giving them a simple “emboss” @mixin with different colors.

@mixin emboss($color)
  background: $color
  +background-image(linear-gradient($color, darken($color, 3%)))
  +box-shadow(darken($color, 10%) 0 0 0 1px inset, lighten($color, 10%) 0 0 0 2px inset)
  +border-radius(3px)

Here are our styles using the “emboss” mixin:

nav
  +emboss(#313D4C)

section.activity
  +emboss(#fff)

section.account
  +emboss(#fff)

Here is the updated wireframe:

emboss added to the elements

Now, we want to style the interactive elements by building on top of our “emboss” @mixin. This will give everything a consistent aesthetic. We can easily update the look and feel later on in the process.

@mixin button($color)
  +emboss($color) // Include our pre-existing @mixin
  display: block
  color: #fff
  border: none
  padding: 8px 15px
  font-size: 11px
  text-decoration: none
  text-transform: uppercase
  font-weight: bold
  cursor: pointer
  +text-shadow(darken($color, 10%) -1px 0 0)

  &:hover
    background-color: lighten($color, 5%)
    +background-image(linear-gradient(lighten($color, 5%), $color))

  &:active
    background-color: darken($color, 5%)
    +background-image(linear-gradient($color, darken($color, 5%)))
    +text-shadow(none)
    +box-shadow(darken($color, 8%) 0 0 0 1px inset, darken($color, 15%) 0 2px 2px -1px inset)

This includes everything we need to make any element (whether that element is a button or an anchor) appear interactive.

Here are some examples using the @mixin:

a.logOut
  +button(#12b512)
  text-transform: none
  margin-top: 10px

button
  +button(#1994F4)

a, a:visited
  +button(#1994F4)

Here’s what our output is looking like:

Using a @mixin makes minute design changes global, which results in reduced time and increased efficiency.

Let’s increase the overall contrast of our box shadows by bumping up the percentage of the darken function on this line:

+box-shadow(darken($color, 20%) 0 0 0 1px inset, lighten($color, 10%) 0 0 0 2px inset)

We can see that the overall contrast of the design (our buttons and boxes) was increased–just from a single change:

Boosted contrast from a single line of code

Tip Two: @include is Messy, Use @extend Instead

@include with @mixin is a big time saver. In the first tip we can see how using them avoids repetitive, redundant work. Now, we could take the more traditional approach of creating a few classes that use @include and then go into our HTML and apply class=”emboss-white”, but that’s messy as well. We can keep the convenience of @include without the bulk. Here's how:

Let’s take a second to see what Compass and Sass have done for us with the “load more” button.

Here is the Sass code:

button
  +button(#1994F4)

And here is the CSS output:

button {
  background: #1994f4;
  background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #1994f4), color-stop(100%, #0c8df2));
  background-image: -webkit-linear-gradient(#1994f4, #0c8df2);
  background-image: -moz-linear-gradient(#1994f4, #0c8df2);
  background-image: -o-linear-gradient(#1994f4, #0c8df2);
  background-image: linear-gradient(#1994f4, #0c8df2);
  -webkit-box-shadow: #085d9f 0 0 0 1px inset, #4aabf6 0 0 0 2px inset;
  -moz-box-shadow: #085d9f 0 0 0 1px inset, #4aabf6 0 0 0 2px inset;
  box-shadow: #085d9f 0 0 0 1px inset, #4aabf6 0 0 0 2px inset;
  -webkit-border-radius: 3px;
  -moz-border-radius: 3px;
  -ms-border-radius: 3px;
  -o-border-radius: 3px;
  border-radius: 3px;
  display: block;
  color: white;
  border: none;
  padding: 8px 15px;
  font-size: 11px;
  text-decoration: none;
  text-transform: uppercase;
  font-weight: bold;
  cursor: pointer;
  text-shadow: #0a79d0 -1px 0 0;
}

button:hover {
  background-color: #319ff5;
  background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #319ff5), color-stop(100%, #1994f4));
  background-image: -webkit-linear-gradient(#319ff5, #1994f4);
  background-image: -moz-linear-gradient(#319ff5, #1994f4);
  background-image: -o-linear-gradient(#319ff5, #1994f4);
  background-image: linear-gradient(#319ff5, #1994f4);
}

button:active {
  background-color: #0b87e8;
  background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #1994f4), color-stop(100%, #0b87e8));
  background-image: -webkit-linear-gradient(#1994f4, #0b87e8);
  background-image: -moz-linear-gradient(#1994f4, #0b87e8);
  background-image: -o-linear-gradient(#1994f4, #0b87e8);
  background-image: linear-gradient(#1994f4, #0b87e8);
  text-shadow: none;
  -webkit-box-shadow: #0a7fda 0 0 0 1px inset, #096bb8 0 2px 2px -1px inset;
  -moz-box-shadow: #0a7fda 0 0 0 1px inset, #096bb8 0 2px 2px -1px inset;
  box-shadow: #0a7fda 0 0 0 1px inset, #096bb8 0 2px 2px -1px inset;
}

The “button” mixin has saved us a lot of time, though you can see how quickly using @include bloats the code.

Instead of rendering the code from @include, @extend will append the selector to a predefined class. This eliminates the need for messing with the HTML, but gives us the benefit of defining styles inline with our CSS.

In fact, Sass has even provided their own selector referred to as the @extend-only selector, or %. This means you can keep your CSS clean of unneeded classes.

Let’s use @extend for our mixins so far:

%emboss-white
  +emboss(#fff)

%emboss-darkBlue
  +emboss(#313D4C)

%button-blue
  +button(#1994F4)

%button-green
  +button(#12b512)

And then updating our Sass to use @extend in place of @mixin:

nav
  @extend %emboss-darkBlue

a.logOut
  @extend %button-green
  text-transform: none
  margin-top: 10px

section.activity
  @extend %emboss-white

button
  @extend %button-blue

section.account
  @extend %emboss-white

a, a:visited
  @extend %button-blue

Because our “load more” button and the controls for the user’s account share the same @extend, this is the output in our CSS file:

button, a, a:visited {
  background: #1994f4;
  background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #1994f4), color-stop(100%, #0c8df2));
  background-image: -webkit-linear-gradient(#1994f4, #0c8df2);
  background-image: -moz-linear-gradient(#1994f4, #0c8df2);
  background-image: -o-linear-gradient(#1994f4, #0c8df2);
  background-image: linear-gradient(#1994f4, #0c8df2);
  -webkit-box-shadow: #085d9f 0 0 0 1px inset, #4aabf6 0 0 0 2px inset;
  -moz-box-shadow: #085d9f 0 0 0 1px inset, #4aabf6 0 0 0 2px inset;
  box-shadow: #085d9f 0 0 0 1px inset, #4aabf6 0 0 0 2px inset;
  -webkit-border-radius: 3px;
  -moz-border-radius: 3px;
  -ms-border-radius: 3px;
  -o-border-radius: 3px;
  border-radius: 3px;
  display: block;
  color: white;
  border: none;
  padding: 8px 15px;
  font-size: 11px;
  text-decoration: none;
  text-transform: uppercase;
  font-weight: bold;
  cursor: pointer;
  text-shadow: #0a79d0 -1px 0 0;
}

button:hover, a:hover, a:hover:visited {
  background-color: #319ff5;
  background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #319ff5), color-stop(100%, #1994f4));
  background-image: -webkit-linear-gradient(#319ff5, #1994f4);
  background-image: -moz-linear-gradient(#319ff5, #1994f4);
  background-image: -o-linear-gradient(#319ff5, #1994f4);
  background-image: linear-gradient(#319ff5, #1994f4);
}

button:active, a:active, a:active:visited {
  background-color: #0b87e8;
  background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #1994f4), color-stop(100%, #0b87e8));
  background-image: -webkit-linear-gradient(#1994f4, #0b87e8);
  background-image: -moz-linear-gradient(#1994f4, #0b87e8);
  background-image: -o-linear-gradient(#1994f4, #0b87e8);
  background-image: linear-gradient(#1994f4, #0b87e8);
  text-shadow: none;
  -webkit-box-shadow: #0a7fda 0 0 0 1px inset, #096bb8 0 2px 2px -1px inset;
  -moz-box-shadow: #0a7fda 0 0 0 1px inset, #096bb8 0 2px 2px -1px inset;
  box-shadow: #0a7fda 0 0 0 1px inset, #096bb8 0 2px 2px -1px inset;
}

This has nearly cut our code output in half. The change may not be obvious in this small, single-page example. However, when we're dealing with a complex web application, we can imagine how many thousands of lines of code this saves. Also, we can use @extend with the Sass indentation to allow absolute specificity, which makes overrides from other selectors less likely.

My rule-of-thumb is that if I ever plan to use the exact same @include more than once, then it should be an @extend.

Tip Three: Calculate Your Colors

In the earlier examples our emboss @mixin was utilizing lighten() and darken() to calculate the gradients and box shadows. This is one of the key features of Sass that has allowed us to stop designing in Photoshop. All we need to know are the base colors in our designs, then we can use calculations for the variations. Using this with variables, we only need to know a hex value once at the start of the project.

For starters, Sass calculates lighten(), darken(), saturate(), desaturate(), and hue(). If we want to get fancy, Sass will even do things like mix() and greyscale().

Let's work off of our base colors in the design to color the typography, then let's use Sass to calculate all the variations on the fly.

$blue: #1994F4
$darkBlue: #313D4C
$green: #12b512

%heading
  text-transform: uppercase
  font-weight: bold
  color: tint($blue, 30%)

body
  background: #edf1f5
  font-size: 13px
  line-height: 1.5em
  color: $darkBlue

section.container

  h1
    @extend %heading
    color: $darkBlue
    margin-top: 40px

nav
  @extend %emboss-darkBlue

  a, a:visited
      @extend %button-darkBlue
      text-transform: none
      color: lighten($darkBlue, 60%)
      text-decoration: none
      display: block
      padding: 5px 20px
      +border-radius(20px)

        &.active

          a, a:visited
            background: darken($darkBlue, 10%)
            +box-shadow(darken($darkBlue, 15%) 0 0 0 1px inset)

a.logOut
    @extend %button-green
    margin-top: 20px

section.main

  section.activity
    @extend %emboss-white

    article
      border-top: 1px solid lighten($darkBlue, 65%)

      &:last-of-type
        border-bottom: 1px solid lighten($darkBlue, 65%)
        margin-bottom: 10px

      p
        font-size: 1.381em
        line-height: 1.2em
        color: lighten($darkBlue, 20%)

        span
          color: lighten($darkBlue, 50%)
          font-size: .618em
          display: block

        a, a:visited
          color: $darkBlue

          &:hover, &:active
            color: $blue

section.account
  @extend %emboss-white

  p
    margin-top: 40px

    a, a:visited
      text-decoration: none
      color: $darkBlue

      &:hover, &:active
        color: $blue

  ul

    li
      margin-right: 10px

      a, a:visited
        @extend %button-blue

With only setting a few HEX values as variables we were able to assign on-the-fly color values to every aspect of the wireframes:

Colors applied to the wireframe calculated on-the-fly

Tip Four: Simple Sprites

Compass can make sprites for you, sprites you never have to think about. Let's review my approach to getting them up and running quickly and how to apply them to any element.

First, we need a folder containing all the images we want to turn into a sprite. Compass will watch this folder for changes each time we save our .sass files, if it detects a new image it will generate a new sprite and update the appropriate @mixin functions.

Using a @mixin with the :after pseudo will allow us to easily apply our sprite image to any element.

All of our images are inside of the css/images/icons folder.

@import compass/utilities/sprites
@import "icons/*.png"
  
@mixin icon($icon, $top, $left)
  position: relative

  &:after
    content: ''
    display: block
    position: absolute
    top: $top
    left: $left
    background: $icons-sprites no-repeat
    +sprite-background-position($icons-sprites, $icon)
    +sprite-dimensions($icons-sprites, $icon)

Compass provides us with two @mixin functions and a single variable ($icon-sprites, based on the name of our folder) for determining the position and size of the sprite. All you need to do is pass the name of the file (without the extension) along with our $icon variable. The rest is simple positioning.

Here is an example of the sprite being used on a few of our buttons:

button
  @extend %button-blue
  padding-left: 25px
  +icon(load, 12px, 8px)

&.profile
  padding-left: 25px
  +icon(profile, 12px, 8px)

&.account
  padding-left: 28px
  +icon(edit, 13px, 8px)

Here is the result:

Sprites added to buttons

Tip Five: The Logical @mixin

The last example gave us easy to use @mixin functions for applying sprite icons to our buttons. However, we were repeating a lot of similar values because most of our buttons are the same size. Only a few of them needed some tweaking to be just right. After looking at this, it may be better to have the “Log Out” icon be to the right of the text instead of the left. This is where having logical mixins–or operators–helps.

Most buttons have the following values:

&.profile
  padding-left: 25px
  +icon(profile, 12px, 8px)

This is a left padding of 25 pixels, and then 12 pixels from the top and 8 pixels from the left. So how do we improve this mixin to save us time and make it easy position the sprite icon to either the left or the right side of the element?

Something like this will do the trick: 

@mixin icon($icon, $direction: left, $padding: 25px, $vertical: 12px, $horizontal: 8px)
  @if $direction == left
    padding-left: $padding
  @else if $direction == right
    padding-right: $padding
  position: relative

  &:after
    content: ''
    display: block
    position: absolute
    top: $vertical
    @if $direction == left
      left: $horizontal
    @else if $direction == right
      right: $horizontal
    background: $icons-sprites no-repeat
    +sprite-background-position($icons-sprites, $icon)
    +sprite-dimensions($icons-sprites, $icon)

Now our three examples above have been simplified to the following, only using variation from our variable defaults where it’s needed:

button
  @extend %button-blue
  +icon(load)

&.profile
  +icon(profile)

&.account
  +icon($icon: edit, $direction: right, $padding: 28px, $vertical: 13px)

If we’re only wanting to pass certain arguments, then all you need to do is define them when you @include your @mixin.

That’s It

Hopefully some of these tips help you think about how Compass and Sass can save you time when you're rapidly prototyping and designing Websites and apps. Got any suggestions? Happy to hear them! Leave them below in the comments. 

Check out the source files on Git.

Special thanks to User Inter Faces for the profile thumbnails!

comments powered by Disqus