A SASS Primer: (less than) 90% of it in (less than) 90 mins.

Trka

Posted by Trka in Code, SASS on Jul 28, 2017


What we're doing: We're playing with SASS. I'm going for a "90% of [blank] in 90 minutes" sort of thing, but we're not covering 90% and hopefully this won't take 90 minutes. More of a dip your toes thing, but we're dipping our toes pretty deep.

Audience: I'm trying to make this information approachable to both designers and developers.

Prerequisites and Assumptions:  Nothing special. You know CSS and you have a basic curiosity about something better. That's all you really need. Maybe you've never heard of SCSS, or maybe you have but you've never sat down with it for 15 minutes for a real discussion.

Preprocessors - especially in the css world - have become pretty well standard practice. I still hear quite a lot of "Why?" or "What the devil is that?!?" from colleagues. This makes me think that, while there's no shortage of documentation on the how and the what, the why may be a little more obscure. 

Let's talk about that. In this post - of course I'm going to end up going into some of the mechanics - but the question I want to answer is "Why do you bother with CSS preprocessors? What's wrong with CSS?"

The short answer to the latter: there's nothing wrong with css. And, frankly, preprocessors add nothing to the language itself. While CSS is great for formatting what's onscreen, it's not terribly geared towards getting there. And this is where preprocessors come in. They make the same output, but they get there exponentially more efficiently. 

Me? I use sass to get there (for the purists, I use SCSS. But that nuance isn't for here/now). LESS and Stylus are also good solutions, but SASS is a little more... ... ....me. Naturally, this post will be using SASS, but similar concepts are in any legitimate preprocessor.

Making big things. We'll get to big things, but let's start with making medium things. Changing a color in css, like a lot of things in CSS, is a find-and-replace operation. search for #cccccc and make it #eeeeee. Fairly simple stuff, but still tedious. It gets a little more tricky if you've used rgb(a) in some places, or if you've used #cdcdcd in a few places, but still simple enough. In the preprocessor world, on the other hand, we've set a variable for that color. $gray: #cccccc. Throughout our SASS, we use $gray instead of the hex, rgb, hsl or whatever. This makes the code infinitely easier to write (especially if you're using an ide with code hinting), but - more than that - when we change our mind about what gray should be, we just change it where we set the variable and the new gray pops into place everywhere we've used it. #magic.

And there's tons of other sugar like that. SASS has functions that let us give it one thing, fiddle it the way we want, and get something different back. If we want 60% opacity on hover, it's not a question of converting hex to rgb and adding an a. It's simply rgba($gray, .6). See what we did there? And others: want one that's 10% lighter or darker? lighten($gray, 10%) or darken($gray, 10%)

It comes out of the box with SASS functions for all sorts of things, but color is where it really shines. Jackie Balzer has made a page that does a pretty good job of showing how a lot of the SASS color functions work. It comes with fun stuff, but what if we need something special just for us? It's really as simple as making a new style rule, but there are some other points we need to cover first. Make a note, though, because we're coming back to that. 

These simple examples with variables and core functions are questionably #magical, but think of a real-world css. You have dozens of colors, margins and paddings, fonts, transitions, and tons of other measures scattered about over several hundred lines of code or more. It's incredibly nice to be able to change something in one place and have everything else just. plain. work. 

Making slightly bigger things. CSS reflects the HTML the way the browser sees it. div#foo > div.header h1.gray{color: #cccccc;} is a great way for a browser to find an h1 that should be gray, if it's inside a header that's directly inside a div with id=foo. 

But we're people. At a glance that's... Well, it's meaningless to us. SASS has nesting. The same piece in SASS looks more like 

$gray: #cccccc;

div#foo{
  > div.header{
    h1.gray{
        color: $gray;
    }
  }
}

And its CSS output is

div#foo > div.header h1.gray {
  color: #cccccc;
}

SO MUCH EASIER TO WORK WITH! Honestly, you could have written that any way you're comfortable; I personally see HUGE value in the style reflecting the DOM, though, so that's how I usually write it. I won't go on too long about best practices or how nesting works, but take a second to look at the code above and consider how it reflects the HTML. Eg: the h1 is inside div.header in the SASS, just like it is in our document. Actually, I'll go on a liiiiitle bit longer, because this is a good time to introduce a light little syntax thing - I wanted to avoid that for a primer, but... it's relevant. SASS and the ampersand. In that example, we made h1.gray, but what if we also want a blue option? SASS has that nesting thing we talked about, which would be awesome for h1 .gray and h1 .blue, but it has a special operator - & - that says "Whatever is just above me, put it riiiiight here". In human-speak, it says "h1 and gray, or h1 and blue.

$gray: #cccccc;
$blue: #3C6E71;

div#foo {
  > div.header {
    h1 {
      &.gray {
        color: $gray;
      }
      &.blue {
        color: $blue;
      }
    }
  }
}

And it makes:

//-- css output
div#foo > div.header h1.gray {
  color: #cccccc;
}
div#foo > div.header h1.blue {
  color: #3C6E71;
}

Without &, we would have styles for h1 .gray and h1 .blue. With the &, we have h1.gray and h1.blue. You already know CSS, so you know how different those two things are. 

Now. Back to plain old nesting. One more very common use for nesting is with qualifiers and style alternates. Let's say you have several different section styles in your site. The text styles are largely the same, but section.section-dark has a dark background and needs light text, while section.section-light has a light background and needs darker text. Even without getting into the more exotic language features, this is dead-simple. Both of these examples are illustrated in the plunker below (plunkers are interactive, if you didn't know. Change variables if you want, and see how they affect the html to the right.). I've intentionally put a less-than awesome thing in this one: the style blocks for .section-dark and .section-light are basically copy-paste. There's a much better way in SASS, but we haven't got to Mixins yet. 

Eh... I don't know... Maybe this is a good time to talk about mixins, before we touch on other things. ...and probably extending also, while we're there. Mixins are chunks of SASS that can be pkugged virtually anywhere you want to mix those rules in. The bit in #001, where the sections' text styles were identical except for the colors is the most basic example of "what  should i use mixins for?". Those blocks are rewritten below, this time using mixins.

Mixin syntax is pretty clean. @mixin name{ //tons of code } and then @include name wherever you want to slip in those tons of code. The parameters we used inside the mixin are purely optional, but you'll almost always use them.

That was... It wasn't a trivial example, but it wasn't huge - maybe a good intro to @mixin. Truth is, mixins are HUGE, and they can be used to make really big things with very little code. And they set us up to talk about that custom functions thing I hinted at a little while ago. Functions, for now, are a whole lot like mixins EXCEPT: mixins make style rules, but functions make values. This Plunker peeks at how a custom function would work for a build-your-own grid layout. The fundamental differences between functions and mixins: 

  • functions use the @function declaration, where mixins use @mixin (naturally) 
  • a function will usually do some thinking inside it, and use the @return keyword to say "I'm done thinking. Return this value, whatever it is" 
  • When you use a mixin, it's sort of in place of a style rule (in a sense, I guess). When you use a function, you basically use it in place of a value - whether it's a dimension or color or whatever your function is setup to do. 

Wait! One more thing about @mixin (a little like when I slipped in the "&" conversation before): the @content keyword. In our last example, we used a mixin's parameters to slide key things into it. @content is a special keyword that lets you slide an entire body of code into the mixin. @content is a pretty simple thing once you wrap your head around mixins in general, but take a second to get comfy with it if you need to. Where before, we used variables to slip values into our mixin's rule, @content is a placeholder that let's our mixin take any stack of styles we feed it. 

Mixins have a close cousin: @extend. Both play the same angle - doing a lot with a little - but there are some important differences. For one, mixins never make styles on their own. They're only used if you @include them. You can have a trillion mixins, but if you never @include them, they'll never make styles and never add to your stylesheet (a powerful idea, by the way). @extend, on the other hand, takes styles that are in place elsewhere and.... fiddles them a bit. Extend is a simpler topic, and may have been a better intro to the idea, but mixins are more exciting, so i led with that. Also, truth be told, for every one time i personally use @extend, I've used @mixin/@include 100 times. Since the question I'm trying to answer is why I use SASS.... Moving on.

And, again, the copy-paste thing. In any of those examples, if your html needs you to rename any of your selectors for some reason, or if you just want to see how a different color or dimension works, just change it in that one place. No searching - the update automagically trickles down.

Make a note of this whole "css reflects the dom" discussion; we'll be coming back to it in a minute when we talk about encapsulation and component approaches. 

Might as well do that now. CSS files get big. Sometimes they get very big. You technically CAN break them out into smaller files, but this adds extra http requests for your page to load. That's bad. Breaking them out really isn't a solution. The SASS world has imports. Imports are external SASS files that get pulled into the main file wherever you keyed @import filename and spit out as a single (minified and with sourcemaps if you're in production) css. 

That's a boring paragraph. We should do some fun (ish) stuff with that idea. 

Since the files are combined and output as a single http request, you can break them out infinitely. I personally do this a lot, breaking my files into logical pieces. Where's the code for that button? Oh, it's probably in buttons.scss. That header style? Yep. typography.scss. Sometimes a file might have 4 lines of code in it, and that's. okay.

It's pretty common to open a SASS file that's been worked on for some time and look at the main style.scss to find ONLY imports. Almost always, the first import will be something like variables.scss which holds - you guessed it - variables, the little dropins that pull everything together. Right after that is usually mixins.scss, a file (or files) with all sorts of little take-it-or-leave it pieces that you can use or not use anywhere you want or don't want. The reason for these global pieces being first is because preprocessors read the source files almost like a tree. You can't use a mixin before it's been loaded, so pulling in your global variables and mixins at the top helps make sure they're available later when you want them. After variables and mixins, there's a stack of other imports like grid, typography, forms, buttons, and components.

Imported files can import others, too. It keeps things very tidy. It's possible for a completed project to have dozens or hundreds of neatly organized sass files. Rather than import all of these in the main style.scss, it's good practice to group them by importing import files. You might see something like @import buttons/index, then look in buttons/_index.scss and find a different stack of 14 button-related imported files, but little or no actual code. 

We're coming very close to something big. I think our next demo will be a pretty complete one that rolls it all together. First, though - there's always a first - let's breeze through some of the fundamentals I didn't want to introduce before, and maybe refresh some of the things we've already discussed. You're going to see them in action in just a second so it's really not critical to know the ins-and-outs right this second, I just don't want your first intro to be a semi-production chunk of code (and your first impression to be "W. T. F?!?!")

  1. @import 'something': (recapping, but adding something) In CSS, all your code is there. In SASS, it can be anywhere. Imports pull in other files where you want to use them.

Imports are relative, and we need to sugar the filenames a little: Imported SASS files have to start with an underscore and end with .sass. So an imported buttons file might be _buttons.scss. You can shorthand this when you import it, though. Because it has to be named that way, sass looks for it that way - rather than @import '_buttons.scss', you use @import 'buttons'.

  1. #{$name} syntax: (new) I'm not going to tell you what this is called because it's a scary word. First, we're going to talk about what it does (it's not that scary). Usually, variables are values. This syntax lets you use variables basically as if you've typed that value in place.
// using a variable as a classname
$name: 'foo';
a.#{$name}{
  color : blue;
}

// using a variable as a string
a{
  $font-size:6;
  font-size: #{$font-size}px;
}

It's more useful than scary, really. The word for it: "String Interpolation"

  1. Maps and @each: (new) Up to now, we've used variables as single values - a pixel size, color value, etc. Maps are a special kind of variable that we can do fun things with. A map is a list of things. It might look like:
$named-palette: (
        primary: #1779ba,
        secondary: #767676,
        success: #3adb76,
        warning: #ffae00,
        alert: #cc4b36,
);

What we did there was set one variable that holds 5 special colors. @each is a magical thing. to use @each on that map, it would look like (explanation coming next, this time it's better to show the scary part first)

@each $color, $value in $named-palette{
  a.#{$color}{
    color: $value;
  }
}

In human-speak, that translates to:

  • Hey, sass, get the $named-palette variable.
  • Now with each one of those things in the list, give me $color from the left side and $value from the right side, and use those to make this rule. The css it makes is:
a.primary {
  color: #1779ba;
}

a.secondary {
  color: #767676;
}

a.success {
  color: #3adb76;
}

a.warning {
  color: #ffae00;
}

a.alert {
  color: #cc4b36;
}

With one list and about 3 magical lines of sass, we can make all the special things we want. You don't always have to @each a map. You can get a specific item from the list anytime using the built-in map-get() function. Consider this code:

a.awesome-link{
  color: map-get($named-palette, primary);
}

Which gives us this css:

a.awesome-link {
  color: #1779ba;
}

Believe it or not, those were simple examples of maps, using what's called a flat map. Maps can also be lists of lists, which lets us do really fun things with the same idea. I'm not going to illustrate it here - it's in the wrap-up demo below, though - because the idea is the same: "You can have lists of things, and you can either do the same thing with everything in the list, or you can pull something specific from that list".

  1. !default: It's a much lighter point than maps, but we should talk about !default, because you're definitely going to see it in the demo.

Variables: You can set them anywhere, and you can reset them anywhere. Files: You can put them anywhere, and you can import them anywhere. !default helps you work-out potential clashes.

Let's say you have a _buttons.scss file that needs a variable called $text-size. Simple enough. In your buttons' variables, set $text-size: 1rem; (or whatever you want it to be). BUT... since _buttons.scss is an imported file, that variable might already be set in the higher-level theme variables, and for consistency's sake, we should respect that. Imagine if the theme is using $text-size: 3rem for some reason, and you re-set it to 1rem. All your buttons will be micro. So, instead, we set it with

$text-size: 1rem !default;

Which translates in human-speak to: "Set $text-size to 1rem if it's not already set. If it is already set, just leave it alone and use what it is".

Last thing, speaking of variables getting along with one another: Scoping This should be the last "let's just touch on this thing" thing. Variables can be set all over the place, so let's go over some of the nuances of how that works.

It really matters where you set a variable and when you re-set it.

  1. Setting a variable inside a nesting level (let's call it level 1) makes it available from there down (levels 1, 2, 3...). It can't be read at a higher nesting level.
  2. If you change it in a deeper level, that change will be seen anywhere else you use it, even outside of that level.The best illustration is probably code, so consider this:
h1 {
  $size: 1rem;
  font-size: $size;

  &.awesome-h1 {
    $size: 2rem;
    font-size: $size; // we re-set it to 2rem for .awesome-h1
  }

  &.better-h1 {
    $size: 0.5rem;
    font-size: $size; // we re-set it to 0.5rem for .better-h1
  }

  margin-bottom: $size; // scopes *will* leak. This is 0.5rem because that's what we set in .better-h1
}

h2{
  font-size: $size; // this is an error because, in h2's scope, $size isn't set
}

Okay. That wraps up all the last-minute breeze-through stuff. Let's play with some code! The Plunker below is going to close us out. I tried to cover all of our points above and roll it all up into a semi-production (albeit light) piece without going too overboard and scaring you away. Plunker does not like fully-built SASS projects. It's good for flipping through, but if you really want to get your hands dirty, instructions are in the Plunker itself.