Sass in the Real World: book 1 of 4

The Turducken mixin: using mixins within a mixin

The power of mixins is not limited to inclusion in the CSS. Just like a variable can be assigned to another variable, a mixin can be included in another mixin allowing for another level of modularity in your stylesheet. As we build a module, for example a button, we are not only building a modular component that can be used as a variety of buttons are created but also we are breaking down our component into smaller components like gradient color, border, padding, and etc...

Let's take a closer look at the assembly of a button. Here are the rules for our .primary-button selector:

.primary-button {
    font-family: 'merriweather_sanslight', sans-serif;
    -moz-box-shadow:inset 0px 1px 0px 0px #ebf5ff;
    -webkit-box-shadow:inset 0px 1px 0px 0px #ebf5ff;
    box-shadow:inset 0px 1px 0px 0px #ebf5ff;
    background:-webkit-gradient( linear,
        left top,
        left bottom,
        color-stop(0.05, #70d9ff),
        color-stop(1, #3481cf) );
    background:-moz-linear-gradient( center top,
                    #70d9ff 5%,
                    #3481cf 100% );
    background-color: #70d9ff;
    border-radius: 0;
    text-indent: 0;
    border: 1px solid #9cceff;
    display: inline-block;
    color: #ffffff;
    font-size: 28px;
    font-weight: normal;
    font-style: normal;
    line-height: 48px;
    padding-left: 30px;
    padding-right: 30px;
    text-decoration: none;
    text-align: center;
}

I have created some mixins in order to handle some of the rules that we use often:

$border-position-all: all !default;
$border-default-size: 1px !default;
$border-default-pattern: solid !default;
$border-default-color: $black !default;

// Mixin arguments set to specified variables as defaults
// This is a mixin that will allow the user to add border to an element.
// It is robust enough to allow the user to select a certain side where the border
// should be applied or it can applied on all sides.
@mixin add-border(
    $border-position: $border-position-all,
    $border-size: $border-default-size,
    $border-pattern: $border-default-pattern,
    $border-color: $border-default-color) {

  @if $border-position == $border-position-all {
    border: $border-size $border-pattern $border-color;
  }
  @else {
    border-#{$border-position}: $border-size
    $border-pattern $border-color;
  }
}

// This function was created in order to be able to take a variable argument, in this case the number
// of color stops for the gradient, and return a comma separated list.
@function linearGradientColors($stop-colors...) {
    $full: false;
    @each $stop-color in $stop-colors{
        @if $full {
            $full: $full + ',' + $stop-color;
        } @else {
            $full: $stop-color;
        }
    }

    $full: unquote($full);

    @return $full;
}

// When creating a function, we place the functional name however for expediency sake, an overloaded
// function is created with a smaller name (usually an acronym of the functions name) so that re-use of the function would be easier.
@function lgc($stop-colors...) {
    @return linearGradientColors($stop-colors...);
}

// This mixin will create a linear gradient using a variable argument for any number of color stops desired.
// The $pos variable allows use to set the gradient line.
@mixin linear-gradient($pos, $stop-colors...) {

  // Detect what type of value exists in $pos
  $pos-type: type-of(nth($pos, 1));

  // If $pos is missing from mixin, reassign vars
  // and add default position
  @if ($pos-type == color) or (nth($pos, 1) == "transparent")  {
    $pos: top; // Default position
    }

  $pos: unquote($pos);

  $full: lgc($stop-colors...);

  // Set the first stop-color as the default fallback color
  $fallback-color: nth(nth($stop-colors, 1), 1);

  background: $fallback-color;
  background: linear-gradient($pos, $full);
}

// This mixin allows us to add box shadows to an element handling the option for an inset box shadow.
@mixin box-shadow ($isInset: false,
                    $hOffset: 0,
                    $vOffset: 0,
                    $blur: 0,
                    $spread: 0,
                    $color: #ccc) {
    @if $isInset {
        box-shadow: inset $hOffset $vOffset $blur $spread $color;
    } @else {
        box-shadow: $hOffset $vOffset $blur $spread $color;
    }
}

These mixins are useful for not only the CSS stylesheets but also can be used anywhere within our Sass environment. It can be used in mixins or functions (although the use in functions is uncommon). Let's take a look at our .primary-button selector and see how I can improve it using some of the above mixins.

While examine the primary-button style, I found the following aspects:

  • The button has four different shades of blue associated to it (#ebf5ff, #70d9ff, #3481cf, #9cceff)
  • The button has a linear gradient
  • The button has a border
  • The button has a inset box shadow

Let's optimize the .primary-button selector using Sass. First I will add the colors of the button to our _config.scss file. However, I will add the primary blue color of the button and all other shades/variations of the color will be used. For example, I could write the colors in the _config.scss file as such:

... //some config variables here
// Primary colors
// -------------------------
$blue:                  #3481CF !default;
$white:                 #FFFFFF !default;
$black:                 #000000 !default;
$gray:                  #7F7F7F !default;

//button colors
// -------------------------
$primary-button-primary-color: $blue !default;
$primary-button-secondary-color: #70D9FF !default;
$primary-button-tertiary-color: #9cceff !default;
$primary-button-quaternary-color: #ebf5ff !default;

Although this is not wrong, it is inefficient and a common mistake many developers new Sass will make. The better approach is to use the color functions that come with Sass to your advantage which will also extend the color scheme so that if the color of the button changes from blue to green, for example, there is only one color to change.

I get the exact colors using Sass' color functions. However, this is also a good time to sit with the designer (if it is not you) and reign in the number of colors used on the site. It's helpful to make the different shades of a color, in this example the blue color of #3481CF, an easy off shoot of the primary color being used. In this manner, I can re-write the above as such:

... //some config variables here
// Primary colors
// -------------------------
$blue:                  #3481CF !default;
$white:                 #FFFFFF !default;
$black:                 #000000 !default;
$gray:                  #7F7F7F !default;

//button colors
// -------------------------
$primary-button-primary-color: $blue !default;
$primary-button-secondary-color:
    adjust-hue(lighten($blue, 11%), -14deg) !default;
$primary-button-tertiary-color: lighten($blue, 36%) !default;
$primary-button-quaternary-color: lighten($blue, 44%) !default;

This approach is advantageous in the following manner:

  • Reduced the number of color used on the site
  • Single point of change when any change would be necessary

Now that I the colors set, I can start re-writing some of the elements of the .primary-button selector. First step, incorporate our linear gradient mixin.

.primary-button {
    @include linear-gradient(center top,
        $primary-button-secondary-color 5%,
        $primary-button-primary-color 100%);
    //... remaining styles
}

Let's add the box shadow styling:

.primary-button {
    @include linear-gradient(center top,
        $primary-button-secondary-color 5%,
        $primary-button-primary-color 100%);
    @include box-shadow (@isInset: true,
                        $vOffset: 1px,
                        $color: $primary-button-quaternary-color);
    //... remaining styles
}

Let's add the borders:

.primary-button {
    @include linear-gradient(center top,
        $primary-button-secondary-color 5%,
        $primary-button-primary-color 100%);
    @include box-shadow (@isInset: true,
                        $vOffset: 1px,
                        $color: $primary-button-quaternary-color);
    //... remaining styles
}

Here is the final style as the mixins are incorporated:

.primary-button {
    @include linear-gradient(center top,
        $primary-button-secondary-color 5%,
        $primary-button-primary-color 100%);
    @include box-shadow (@isInset: true,
                        $vOffset: 1px,
                        $color: $primary-button-quaternary-color);
    @include add-border($border-color: $primary-button-tertiary-color);
    border-radius: 0;
    display: inline-block;
    color: #ffffff;
    font: {
        size: 28px;
        weight: normal;
        style: normal;
        family: 'merriweather_sanslight', sans-serif;
    }
    line-height: 48px;
    padding-left: 30px;
    padding-right: 30px;
    text-indent: 0;
    text-decoration: none;
    text-align: center;
}

By incorporating the mixins, I have not only re-used code, but also in further implementations of this button, whether it be a different type of button or the implementation of pseudo classes like :hover, I can further use and extend this code base.