On Github minamarkham / supercharge-sass
I’m Mina. I'm a front-end developer from Austin.
I currently work at IBM Design. BTW, we're hiring.
I'm a co-organizer of Front Porch, and also teach for Girl Develop It.
Client: Crumbs & Creme
Description: A local cupcakery website.
Framework: HTML5 & CSS3
Slides + Project: mina.so/super-sass
Nothing too special, just something to play around with - but feel free to go all out!
I HIGHLY recommend that you DL these slides and refer back to them as we're going through exercises
You're welcome to follow the examples exactly, or customize things as we go.
And Feel free to try things out as I'm presenting, I don't want you to feel like you need to wait for the exercise to experiment.
“Sass is an extension of CSS that adds power and elegance to the basic language. It allows you to use variables, nested rules, mixins, inline imports, and more, all witha fully CSS-compatible syntax.”
— Sass Documentation Any valid CSS file is a valid Sass fileCSS Extension
SassScript Language
CSS Preprocessor
SassScript Interpreter
Sass ➳ CSS
Sass is an extension of CSS, but we can't load Sass in the browser like we can CSS
May seem like 2 separate tools, but they're really just one, and more often then not, you'll hear them referred to as a singluar tool.
.scss (Sassy CSS)Default syntax; Valid CSS == Valid SCSS
.button { border: 2px solid #f37; background-color: #f69; }
.sass (Indented)Original syntax, still supported; Haml-esque
.button border: 2px solid #f37 background-color: #f69
Because valid CSS is also valid SCSS, getting started with Sass is as easy as changing the file extension.
Start slow, add in features as you learn / get more comfortable.
I'm going to talk a little bit about the tools of using Sass—how to install it, what apps and options there are—and then we'll go back to the why and discuss some authoring tricks that help me, and hopefully can help you.
gem install sass
works with single files and entire directories. let's look at some more commands
Sourcemaps tell browsers where to find the Sass that generated the CSS. VERY helpful for debugging.You must enable sourcemaps in the browser to use this feature.
Alright, so now that we know generally what Sass is and how it works.
Let's get into the basics
Sass files are split into "partials" for better organization.
An _underscore creates a partial
Partials will not compile to .css on their own, they must be @imported
Partials allow us to break up our code into more modular, maintainable chunks!
You can then import those partials into your main Sass file.
@import 'utilities/index'; @import 'lib/index'; @import 'base/index'; @import 'layout/index'; @import 'modules/index'; @import 'states/index'; @import 'utilities/fonts'; @import 'shame';
CSS @import has always meant an extra file download.
Sass modifies @import to instead include the resource during compilation, rather than on the client side.
What does this mean?
Now that we've grouped all our styles together
We can now break these groups out into smaller, maintainable modules
sass --watch scss:css
Nesting allows you to organize your code in relevant chunks
.scss
pre { margin: 0; width: auto; box-shadow: none; code { background: transparent; border: 6px solid #fff; box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); min-height: 300px; max-height: 475px; padding: 5px 10px; } }
.css
pre { margin: 0; width: auto; box-shadow: none; } pre code { background: transparent; border: 6px solid #fff; box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.3); min-height: 300px; max-height: 475px; padding: 5px 10px; }
For nesting, follow the inception rule.
http://thesassway.com/beginner/the-inception-rule
Work WITH the cascade, not against it.
Nesting too deeply results in specificity nightmares and fragile, unmaintainable code
Here's an extreme example
body { div.container { div.content { div.articles { & > div.post { div.title { h1 { a { } } } div.content { p { ... } ul { li { ... } } } div.author { a.display { img { ... } } h4 { a { ... } } p { a { ... } } ul { li { ... } } } } } } } }
So you might write this...
Using the above code, you can predict 100% of the time what's going to happen with your stylesheet. There is no cascading that can beat the specificity …
After compiling the Sass, we take a look at the output...
body { ... } body div.content div.container { ... } body div.content div.container div.articles { ... } body div.content div.container div.articles > div.post { ... } body div.content div.container div.articles > div.post div.title { ... } body div.content div.container div.articles > div.post div.title h1 { ... } body div.content div.container div.articles > div.post div.title h1 a { ... } body div.content div.container div.articles > div.post div.content { ... } body div.content div.container div.articles > div.post div.content p { ... } body div.content div.container div.articles > div.post div.content ul { ... } body div.content div.container div.articles > div.post div.content ul li { ... } body div.content div.container div.articles > div.post div.author { ... } body div.content div.container div.articles > div.post div.author a.display { ... } body div.content div.container div.articles > div.post div.author a.display img { ... } body div.content div.container div.articles > div.post div.author h4 { ... } body div.content div.container div.articles > div.post div.author h4 a { ... } body div.content div.container div.articles > div.post div.author p { ... } body div.content div.container div.articles > div.post div.author p a { ... } body div.content div.container div.articles > div.post div.author ul { ... } body div.content div.container div.articles > div.post div.author ul li { ... }
The ampersand will reference the entire chain of parent selectors
This is great for SMACSS, BEM, & other namespacing methodologies.
No selectors from hell!
The & selector can follow other selectors.
a { .footer & { text-decoration: none; span { opacity: .5; } } span { .navigation & { display: block; } }
.footer a { text-decoration: none; } .footer a span { opacity: .5; } .navigation a span { display: block; }
So this can be really really helpful, especially if you're familiar with the concept of modifiers as they relate to the concepts of OOCSS and BEM.
What you're essentially doing is modifying the parent selector
We'll jump quickly into OOCSS and BEM later, but you'll see hints of it throughout the shop
The & selector can also be combined with strings.PERFECT for BEM, child elements, states, and modifications.
.scss
.nav { &-list { font-size: 25px; line-height: 1; margin: 0; line-height: 63px; float: right; > li { padding: 0 5px !important; line-height: 1; padding: 8px 0; &:first-child { padding-left: 0 !important; } &:last-child { padding-right: 0 !important; } } } }
.css
.nav-list { font-size: 25px; line-height: 1; margin: 0; line-height: 63px; float: right; } .nav-list > li { padding: 0 5px !important; line-height: 1; padding: 8px 0; } .nav-list > li:first-child { padding-left: 0 !important; } .nav-list > li:last-child { padding-right: 0 !important; }
AGAIN this can be really really helpful, especially if you're familiar with the concept of modifiers as they relate to the concepts of OOCSS and BEM.
What you're essentially doing is modifying the parent selector
We'll jump quickly into OOCSS and BEM later, but you'll see hints of it throughout the shop
You can also nest namespaced properties
a { border: { color: #deedee; style: solid; width: 2px; } }
a { border-color: #deedee; border-style: solid; border-width: 2px; }
Not only can you nest selectors
ALRIGHT, so that was nesting, let's move on to the next basic...
NEST ALL THE THINGS!!
BUT Don't nest too deeply! And just kidding... you don't need to nest everything. P.S. Don't forget the parent & selector.sass --watch scss:css
Placeholders for values used multiple times.
.scss
$font-family-display: 'Pacifico', cursive; $pink: #fe4578; h1 { font-family: $font-family-display; background: $pink; }
.css
h1 { font-family: 'Pacifico', cursive; background: #fe4578; }
.scss
// Descriptive colors: $hopbush: #c69; $bouquet: #b37399; // Main color palette $color-primary: $hopbush; $color-secondary: $bouquet; .btn {background: $color-primary;} .btn-secondary {background: $color-secondary;}
.css
.btn {background: #c69;} .btn-secondary {background: #b37399;}
Variables are only available within the level of nested selectors where they're defined. If they're defined outside of any nested selectors, they're available everywhere.
— Sass Documentation$border-width: 4px; // Global a { $color: orange; // Local border: $border-width solid $color; } p { border: $border-width solid $color; // ERROR!! Undefined variable "$color" }
This is sort of a global / local setup
Globals are a bit more flexible, let's take a look
sass --watch scss:css
So we're not going to focus too much on globals or overrides or interpolation at this stage, let's just get comfortable with setting and using variables
Mixins are bits of code you @include elsewhere in your styles.
.scss
@mixin mq($breakpoint, $query: 'min-width', $type: 'screen') { @media #{$type} and (#{$query}: #{$breakpoint}) { @content; } } .cupcake { width: 450px; @include mq(500px) {width: 200px;}; }
.css
.cupcake { width: 450px; @media screen and (min-width: 500px) { width: 200px; } }
Mixins are great for repeated blocks of styles where the values differ from case to case.
@mixin rounded-corners($radius: 5px) { -webkit-border-radius: $radius; -moz-border-radius: $radius; border-radius: $radius; } .header { @include rounded-corners(5px); // ... } .footer { @include rounded-corners(10px); // ... }
.header { -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; // ... } .footer { -webkit-border-radius: 10px; -moz-border-radius: 10px; border-radius: 10px; // ... }
And this is where we get into arguments
Dynamic VS Static
@mixin mq($breakpoint, $query: 'min-width', $type: 'screen') { @media #{$type} and (#{$query}: #{$breakpoint}) { @content; } } .cupcake { width: 450px; @include mq(500px) {width: 200px;}; } .cupcake-with-toppings { width: 450px; @include mq(300px) {width: 100px;}; }
.cupcake { width: 450px; @media screen and (min-width: 500px) { width: 200px; } } .cupcake-with-toppings { width: 450px; @media screen and (min-width: 300px) { width: 100px; } }
This helps us create even more dynamic mixins
We can do this sort of interpolation all over the place, not just in mixins
alright... so I've put together a couple of realistic use cases for mixins just to give you some ideas...
sass --watch scss:css
Extending allows selectors to inherit the styles of other selectors, while adding more styles to the element.
.scss
.cupcake { background-image: url("../img/cupcake.png"); } .cupcake-with-toppings { @extend .cupcake; &:after { content: " "; background-image: url("../img/toppings.png"); } }
.css
.cupcake, .cupcake-with-toppings { background-image: url("../img/cupcake.png"); } .cupcake-with-toppings:after { content: " "; background-image: url("../img/toppings.png"); }
.cupcake
.cupcake-with-toppings
Placeholders are "invisible" until extended
.scss
// this will not output %cupcake { background-image: url("../img/cupcake.png"); } // this will .cupcake-with-toppings { @extend %cupcake; &:after { content: " "; background-image: url("../img/toppings.png"); } }
.css
.cupcake-with-toppings:after { content: " "; background-image: url("../img/toppings.png"); }
%cupcake
.cupcake-with-toppings
@includes duplicates CSS
use when you need to alter variables@extend comma-delineates selectors
reuse code with no extra output@extend is an easy way for one selector to share the styles of another selector, without duplicating the lines of CSS in your output
sass --watch scss:css
Maps are structured in key: value pairs and accessed with map-get($map-name, key).
.scss
$zindex: ( modal : 9000, overlay : 8000, dropdown : 7000, header : 6000, footer : 5000, pseduo : 4000, bottom : -999999 ); .dropdown { z-index: map-has-key($zindex, modal); }
.css
.dropdown { z-index: 7000; }
Maps must always be surrounded by parentheses.
Key-value pairs must always be comma separated.
Maps can be looped through or accessed via built-in map functions.
sass --watch scss:css
Use @function to define the function, @return to get the value.
@function fluidize($target, $context) { @return ($target / $context) * 100%; } .sidebar { width: fluidize(350px, 1000px); // => width: 35%; }We define a function with the @function directive. We return a value with the @return directive. We call a function by it's name and parens, passing arguments if needed. Functions must be defined before they're referenced. Functions accept arguments, they have the same rules as mixins (ie. defaults, order, keyword args, etc.)
sass --watch scss:css
$fonts: 'YanoneKaffeesatz-ExtraLight', 'YanoneKaffeesatz-Light', 'YanoneKaffeesatz-Regular', 'YanoneKaffeesatz-Bold';
.scss
$fonts: 'YanoneKaffeesatz-ExtraLight', 'YanoneKaffeesatz-Light', 'YanoneKaffeesatz-Regular', 'YanoneKaffeesatz-Bold'; @each $font-face in $fonts { @font-face { font-family: $font-face; src: url('../fonts/#{$font-face}.eot'); // IE9 Compat Modes src: url('../fonts/#{$font-face}.eot?#iefix') format('embedded-opentype'), // IE6-IE8 url('../fonts/#{$font-face}.woff') format('woff'), // Modern Browsers url('../fonts/#{$font-face}.ttf') format('truetype'), // Safari, Android, iOS url('../fonts/#{$font-face}.svg##{$font-face}') format('svg'); // Legacy iOS font-style: normal; font-weight: normal; } }
.css
@font-face { font-family: "YanoneKaffeesatz-ExtraLight"; src: url("../fonts/YanoneKaffeesatz-ExtraLight.eot"); src: url("../fonts/YanoneKaffeesatz-ExtraLight.eot?#iefix") format("embedded-opentype"), url("../fonts/YanoneKaffeesatz-ExtraLight.woff") format("woff"), url("../fonts/YanoneKaffeesatz-ExtraLight.ttf") format("truetype"), url("../fonts/YanoneKaffeesatz-ExtraLight.svg#YanoneKaffeesatz-ExtraLight") format("svg"); font-style: normal; font-weight: normal; } @font-face { font-family: "YanoneKaffeesatz-Light"; src: url("../fonts/YanoneKaffeesatz-Light.eot"); src: url("../fonts/YanoneKaffeesatz-Light.eot?#iefix") format("embedded-opentype"), url("../fonts/YanoneKaffeesatz-Light.woff") format("woff"), url("../fonts/YanoneKaffeesatz-Light.ttf") format("truetype"), url("../fonts/YanoneKaffeesatz-Light.svg#YanoneKaffeesatz-Light") format("svg"); font-style: normal; font-weight: normal; } @font-face { font-family: "YanoneKaffeesatz-Regular"; src: url("../fonts/YanoneKaffeesatz-Regular.eot"); src: url("../fonts/YanoneKaffeesatz-Regular.eot?#iefix") format("embedded-opentype"), url("../fonts/YanoneKaffeesatz-Regular.woff") format("woff"), url("../fonts/YanoneKaffeesatz-Regular.ttf") format("truetype"), url("../fonts/YanoneKaffeesatz-Regular.svg#YanoneKaffeesatz-Regular") format("svg"); font-style: normal; font-weight: normal; } @font-face { font-family: "YanoneKaffeesatz-Bold"; src: url("../fonts/YanoneKaffeesatz-Bold.eot"); src: url("../fonts/YanoneKaffeesatz-Bold.eot?#iefix") format("embedded-opentype"), url("../fonts/YanoneKaffeesatz-Bold.woff") format("woff"), url("../fonts/YanoneKaffeesatz-Bold.ttf") format("truetype"), url("../fonts/YanoneKaffeesatz-Bold.svg#YanoneKaffeesatz-Bold") format("svg"); font-style: normal; font-weight: normal; }
$theme: light !default; body { @if $theme == dark { background: #000;} @else if $theme == dark-mid { background: #4f4f4f;} @else if $theme == light-mid { background: #afafaf;} @else { background: #fff;} }We begin a conditon with the @if directive, then give it a comparison to evaluate. We have @else if for additional comparisons. And @else as a fallback if all prior comparisons evaluate to null or false.
.scss
$fonts: 'YanoneKaffeesatz-ExtraLight', 'YanoneKaffeesatz-Light', 'YanoneKaffeesatz-Regular', 'YanoneKaffeesatz-Bold'; @each $font-face in $fonts { @font-face { font-family: $font-face; src: url('../fonts/#{$font-face}.eot'); // IE9 Compat Modes src: url('../fonts/#{$font-face}.eot?#iefix') format('embedded-opentype'), // IE6-IE8 url('../fonts/#{$font-face}.woff') format('woff'), // Modern Browsers url('../fonts/#{$font-face}.ttf') format('truetype'), // Safari, Android, iOS url('../fonts/#{$font-face}.svg##{$font-face}') format('svg'); // Legacy iOS font-style: normal; font-weight: normal; } }
.css
@font-face { font-family: "YanoneKaffeesatz-ExtraLight"; src: url("../fonts/YanoneKaffeesatz-ExtraLight.eot"); src: url("../fonts/YanoneKaffeesatz-ExtraLight.eot?#iefix") format("embedded-opentype"), url("../fonts/YanoneKaffeesatz-ExtraLight.woff") format("woff"), url("../fonts/YanoneKaffeesatz-ExtraLight.ttf") format("truetype"), url("../fonts/YanoneKaffeesatz-ExtraLight.svg#YanoneKaffeesatz-ExtraLight") format("svg"); font-style: normal; font-weight: normal; } @font-face { font-family: "YanoneKaffeesatz-Light"; src: url("../fonts/YanoneKaffeesatz-Light.eot"); src: url("../fonts/YanoneKaffeesatz-Light.eot?#iefix") format("embedded-opentype"), url("../fonts/YanoneKaffeesatz-Light.woff") format("woff"), url("../fonts/YanoneKaffeesatz-Light.ttf") format("truetype"), url("../fonts/YanoneKaffeesatz-Light.svg#YanoneKaffeesatz-Light") format("svg"); font-style: normal; font-weight: normal; } @font-face { font-family: "YanoneKaffeesatz-Regular"; src: url("../fonts/YanoneKaffeesatz-Regular.eot"); src: url("../fonts/YanoneKaffeesatz-Regular.eot?#iefix") format("embedded-opentype"), url("../fonts/YanoneKaffeesatz-Regular.woff") format("woff"), url("../fonts/YanoneKaffeesatz-Regular.ttf") format("truetype"), url("../fonts/YanoneKaffeesatz-Regular.svg#YanoneKaffeesatz-Regular") format("svg"); font-style: normal; font-weight: normal; } @font-face { font-family: "YanoneKaffeesatz-Bold"; src: url("../fonts/YanoneKaffeesatz-Bold.eot"); src: url("../fonts/YanoneKaffeesatz-Bold.eot?#iefix") format("embedded-opentype"), url("../fonts/YanoneKaffeesatz-Bold.woff") format("woff"), url("../fonts/YanoneKaffeesatz-Bold.ttf") format("truetype"), url("../fonts/YanoneKaffeesatz-Bold.svg#YanoneKaffeesatz-Bold") format("svg"); font-style: normal; font-weight: normal; }
The @each directive takes the form of: @each $var in <list>
$var can be any variable name
<list> could be a list, or a variable storing one
Loops like this can be really helpful for generating repetetive and calculated style blocks - grid columns are a good example
.scss
$columns: 4; @for $i from 1 through $columns { .cols-#{$i} { width: ((100 / $columns) * $i) * 1%; } }
.css
.cols-1 { width: 25%; } .cols-2 { width: 50%; } .cols-3 { width: 75%; } .cols-4 { width: 100%; }
.scss
$columns: 4; @for $i from 1 to $columns { .cols-#{$i} { width: ((100 / $columns) * $i) * 1%; } }
.css
.cols-1 { width: 25%; } .cols-2 { width: 50%; } .cols-3 { width: 75%; }
“The @while loop has absolutelyno use case in a real Sass project, especially since there is no way to break a loop from the inside.Do not use it.”
— Hugo Giraudel, Sass Guidelinessass --watch scss:css
If you are starting a new project—or refactoring an old one—the way you organize your files can be one of the early things you do. I hear questions about this maybe more than anything else: how do you structure your projects?
Like anything, the main answer is: it depends. Every project is different. There are infinite ways to do it, so there is no blanket answer. but there are some best practices that I think most people aim to follow.
Modular architecture is the abstraction of repetition into "objects".
It's a programmatic approach to styles.
3 parts to these objects : modules, parent-child relationships, and modifiers
2 key components to making this work : classes and naming conventions
goals are 3-fold
modularity: patterns, categorization
scalability: cascade is king
maintainability: modules are the basis for new pages, we might not need to write any new CSS
DRY: REUSE
many people take a custom approach, there are several established frameworks...
Here's an example of my preferred style structure. I'll go over it more in detail in a moment.
Styles are broken down into the following groups: Base, Layout, Modules, States, Themes
+ scss/ | | + base/ # reset, typography, site-wide | |-- _index.scss # imports for all base styles | |-- _base.scss # base styles | |-- _normalize.scss # normalize v3.0.1 | | + layout/ # major components, e.g., header, footer etc. | |-- _index.scss # imports for all layout styles | | + modules/ # minor components, e.g., buttons, widgets etc. | |-- _index.scss # imports for all modules | | + states/ # js-based classes, alternative states | |-- _index.scss # imports for all state styles | |-- _states.scss # state rules | |-- _print.scss # print styles | |-- _touch.scss # touch styles | | + themes/ # (optional) separate theme files | |-- beccapurple.scss # rename to appropriate theme name | | + utilities/ # non-CSS outputs (i.e. mixins, vars) | |-- _index.scss # imports for all mixins + global project vars | |-- _fonts.scss # font mixins | |-- _functions.scss # ems to rems conversion, etc. | |-- _global.scss # global variables | |-- _helpers.scss # placeholder helper classes | |-- _mixins.scss # media queries, CSS3, etc. | | + lib/ # third party styles | | _index.scss # imports for third party styles | | _pesticide.scss # CSS pesticide | | ... | | + ie.scss # IE specific Sass file | + styles.scss # primary Sass file | + _shame.scss # because hacks happen
If you are just trying to test something, see if something works, or just want to play around, I recommend sassmeister.com, developed by Jed Foster (http://jedfoster.com/) and Dale Sande (http://www.dalesande.com/).
You can use SCSS or Sass syntax, different/new versions of the Sass compiler, plugins such as Compass, Bootstrap and Foundation, Bourbon, Breakpoint, Jacket, Singularity, Susy, and even True, the testing framework.
mass @extending can create crazy long selector groups, <IE9 4095 selectors per file limit
@minamarkham :: Squares 2015