Get Sassy

Who Am I?

Code for Today

Go to

https://github.com/Snugug/code-get-sassy

to fork and clone the repo

Each challenge's starting point is available as a branch.

If you would like to be caught up quickly, simply check out the branch of the challenge you're on.

What Is Sass?

Sass is a language construct to create CSS

It makes writing awesome CSS easier

sass-lang.com

Sass has TWO syntaxes:

.sass and .scss

.sass - whitespace sensitive

// .sass
.my-selector
  font-size: 2em
  background: #333
  font-family: $monospace
// .scss
.my-selector {
  font-size: 2em;
  background: #333;
  font-family: $monospace;
}

.scss - superset of CSS

Today we're using SCSS

Tech reasons

  • Easier to understand if you know CSS
  • Easier to integrate in the real-world


Team reasons

  • Helps onboard CSS-savvy teammates.
  • SCSS allows you to convert a project
    piece by piece even in-progress

Compass

Compass is a framework built around Sass, enhancing Sass in two main areas

  • Extension framework for sharing and using code
  • Sass Framework for assisting with cross-browser compatibility and development needs

The core of a Compass project is the config.rb file, which lives at the root of your working directory:

  • Provides a pseudo asset pipeline so Compass knows where your different file types should live
  • Compilation options for Compass
  • Extension requires for including Compass extensions

Compiling with Compass can be done in one of two ways

  • compass compile One-off compile of your Sass files
  • compass watch Listener for changes to Sass files; will trigger a recompile with each change

Dependency Management

Sass and Compass are Ruby projects, so you need to have Ruby installed

Handling Ruby dependencies is easy with Bundler

When working on different computers, developers, or even projects, there will always be an issue with ruby gems changing between different versions.

We want to make sure that we know which versions of each gem we are using to prevent compiling differences and errors. Bundler allows us to do just that. But first, we need to make sure we have Bundler installed:

gem install bundler

The Gemfile that comes with the project will tell Bundler exactly what versions of what gems to use. Use the following command to install your gem dependencies and create a Gemfile.lock to lock each gem to the correct version.

bundle install

From now on, instead of running "compass watch" or "compass compile", you use:

bundle exec compass watch

And bundler will only use the gem versions selected within the .lock file

Challenge 01: Dependencies

  • Install your dependencies through Bundler
  • Don't install them globally! Instead, install them to a path in your folder called vendor

Challenge 02: Creating Some Sass

  • Create a folder for your Sass files, and put a file called style.scss in it
  • Make the <h1> in index.html red in your style.scss file

Challenge 03: Compass Settings

  • Change the stylesheets directory to css, the javascripts directory to js, and the images directory to img
  • Turn on relative assets

Sass Basics

Sass makes maintaining pre-compiled CSS easier by extending pre-compiled CSS in 3 useful ways:

  • Variables
  • Selector Nesting
  • Selector Extension and Placeholder Selectors

Variables

Variables in Sass begin with a $ and can store any single value (i.e. no properties, just values)

$color: #c0ffee; // rgb(a) and hsl(a) work as well
$string: 'Hello World';
$number: 100px; // With or without unit
$list: 'alpha' 'beta', 'gamma' 'delta';
$booleans: true;
$nulls: null;
$maps: (where: waldo, garfield: lasagna); // Sass 3.3+

Variables can be used as values in properties or other variables and as inputs in functions and mixins

$red: #fc2c3a;
$primary-color: $red;

.foo {
    color: $primary-color;
    background: mix(black, $primary-color, 25%);
}
.foo {
  color: #fc2c3a;
  background: #bd212b;
}

Variables can also be interpolated into strings, selectors, or properties

$greeting-class: 'welcome';
$greeting: 'Aloha';

.#{$greeting-class}:before {
	content: '#{$greeting} from Universal Studios';
}
.welcome:before {
  content: "Aloha from Universal Studios";
}

One of the most common ways to get started with Sass is to take an existing CSS project, move it into your Sass directory, change it to a .scss file, then convert colors and font stacks to variables

body {
  background: #333;
  color: #eee;
  font-family: 'Papyrus' 'Comic Sans' 'Helvetica' sans-serif;
}

h1 {
  font-family: 'Times New Roman' serif;
}
$background-color: #333;
$font-color: #eee;

$font-sans: 'Papyrus' 'Comic Sans' 'Helvetica' sans-serif;
$font-serif: 'Times New Roman' serif;

body {
  background: $background-color;
  color: $font-color;
  font-family: $font-sans;
}

h1 {
  font-family: $font-serif;
}

Selector Nesting

Sass allows you to nest selectors inside each other to create compound selector chains

A common mistake is to use nesting to make your selector chain look like your DOM structure; don't do that!

We like to use the Inception rule: never go more than 3 levels deep!

.navigation {
  background: black;

  li {
    display: inline-block;
  }

  a {
    color: white;
  }
}
.navigation {
  background: black;
}
.navigation li {
  display: inline-block;
}
.navigation a {
  color: white;
}
.navigation {
  background: black;

  ul {
    li {
      display: inline-block;

      a {
        color: white;
      }
    }
  }
}
.navigation {
  background: black;
}
.navigation ul li {
  display: inline-block;
}
.navigation ul li a {
  color: white;
}

When nesting, we can control where our nested selector gets placed thanks to the wonderful, magical ampersand &

Placing the & adjacent to your selector will attach it directly to its parent, and putting it after will place it before its parent

a {
  color: blue;
  text-decoration: underline;
  &:hover {
    color: mix(white, blue, 25%);
    text-decoration: none;
  }
}

.logo {
  .svg & {
    background-image: url('../img/logo.svg');
  }
  .no-svg & {
    background-image: url('../img/logo.png');
  }
}
a {
  color: blue;
  text-decoration: underline;
}
a:hover {
  color: #3f3fff;
  text-decoration: none;
}

.svg .logo {
  background-image: url("../img/logo.svg");
}
.no-svg .logo {
  background-image: url("../img/logo.png");
}

With the introduction of Sass 3.3, we now have more control over how to construct and place selectors with @at-root and #{&}

  • @at-root Places encapsulated code at the root of the document
  • #{&} Interpolates the current parent selector
.message {
  color: white;

  #{&}--link {
    color: blue;
  }

  @at-root #{&}--status {
    background: green;
  }

  @at-root #{&}--error {
      background: red;
  }
}
.message {
  color: white;
}
.message .message--link {
  color: blue;
}
.message--status {
  background: green;
}
.message--error {
  background: red;
}

Selector Extension and Placeholder Selectors

Sass introduces the concept of selector extension as a way of specifying that a selector inherits the properties of another selector.

This inheritance takes place in CSS by comma-separating the selectors in the output CSS

Warning: If not used with forethought, extension can bloat your output CSS

.message {
  border-radius: 5px;
  padding: 5px;
}

.message--status {
  @extend .message;
  background-color: green;
}
.message--error {
  @extend .message;
  background-color: red;
}
.message, .message--status, .message--error {
  border-radius: 5px;
  padding: 5px;
}

.message--status {
  background-color: green;
}
.message--error {
  background-color: red;
}
a {
  color: blue;
  text-decoration: underline;

  nav & {
    color: white;
    text-decoration: none;
  }

  #very-specific & {
    color: green;
  }
}

.bad-extensions {
  @extend a;
}
a, .bad-extensions {
  color: blue;
  text-decoration: underline;
}

nav a, nav .bad-extensions {
  color: white;
  text-decoration: none;
}

#very-specific a, #very-specific .bad-extensions {
  color: green;
}

Placeholder Selectors give us fine-grain control over using @extend by providing a silent class to extend from

Placeholder selectors inherit the the name of the first selector to extend them, reducing our total output and ensuring we're extending only what we want to

Placeholder selectors begin with a %

%message {
  border-radius: 5px;
  padding: 5px;
}

.message--status {
  @extend %message;
  background-color: green;
}
.message--error {
  @extend %message;
  background-color: red;
}
.message--status, .message--error {
  border-radius: 5px;
  padding: 5px;
}

.message--status {
  background-color: green;
}
.message--error {
  background-color: red;
}

Challenge 04: Fonts & Colors

  • Create a serif and sans-serif font stack
  • Choose primary, secondary, and tertiary colors, and make your <h1> your primary color

Challenge 05: Headers & Paragraphs

  • Add <h2> through <h6> tags to your index.html, as well as a <p> tag, all with some text
  • <h1>, <h2>, <h3> should have a serif font and <h4>, <h5>, <h6>, <p> should have a sans-serif font
  • <h2>, <h3>, <h4> should be your secondary color

Challenge 06: Organization

  • Create colors, typography, and extendables partials inside of sass/partials
  • Move your font variables and color variables into their respective partials
  • Move your placeholder selectors into your extendables partial

Sass Constructs

Sass has two main constructs to assist in writing CSS

  • Functions
  • Mixins

Functions

Functions provide a way for you to process information and @return a single variable

Functions require a @return, and can return any data type that can be stored as a variable

They can take both required or optional arguments.

@function greeting($phrase, $place: 'Islands of Adventure') {
  $message: $phrase + ' from ' + $place;
  @return $message;
}

.hello--spanish {
  content: greeting('¡Hola');
}
.hello--french {
  content: greeting('Bonjour', 'Universal Studios');
}
.hello--spanish {
  content: "¡Hola from Islands of Adventure";
}

.hello--french {
  content: "Bonjour from Universal Studios";
}

Functions cannot be called at the root of a document; their results need to be assigned to a variable, a property, or interpolated as a string

Sass has a number of very useful built in functions ready to be used:

  • Color Functions
  • Number Functions
  • String Functions
  • Introspection and Miscellaneous Functions
  • List Functions
  • Map Functions

Select Color Functions

  • mix($color-1, $color-2, [$weight]) - Mixes two colors together
  • complement($color) - Returns the complement of a color
  • invert($color) - Returns the inverse of a color
  • rgba($color, $alpha) - Changes the alpha component for a color. Color can be passed in as any valid color format.
  • red($color), green($color), blue($color), hue($color), saturation($color), lightness($color) - Returns relevant components of a color on the rgb/hsl scale

Number Functions

  • percentage($value) - Converts a unitless number to a percentage
  • abs($value) - Returns the absolute value of a number
  • round($value), floor($value), ceil($value) - Rounds a number to the nearest whole number
  • min($numbers...), max($numbers...) - Finds the min/max of several numbers

String Functions

  • str-length($string) - Returns the number of characters in a string
  • str-insert($original, $insert, $index) - Inserts one string into another at a given point. An index of 0 will insert it at the beginning of the string
  • str-index($string, $substring) - Returns the index of the first occurrence of the substring. Returns 0 if substring doesn't exist, 1 if it's the first character
  • str-slice($string, $start-at, [$end-at]) - Extracts a substring from the input string
  • to-upper-case($string), to-lower-case($string) - Transforms string to upper/lower case

Introspection & Miscellaneous Functions

  • type-of($value) - Returns the primitive data type of the value
  • unit($number) - Returns the unit(s) associated with a number
  • unitless($number) - Returns whether a number has units
  • comparable($number-1, $number-2) - Returns wether two numbers can be addes, subtracted, or compared
  • if($condition, $if-true, $if-false) - Returns one of two values , depending on whether or not the condition is true

List Functions

  • length($list) - Returns the length of a list (or number of pairs in a map)
  • nth($list, $n) - Returns the nth item in a list/map. Sass starts counting at 1, not 0.
  • join($list1, $list2, $separator) - Joins two lists together. The separator can either be a comma or a space
  • append($list, $val, $separator) - Appends a single value onto the end of a list
  • zip($lists...) - Combines several lists into a single multidimensional list
  • index($list, $value) - Returns the position of a value within a list. If the value isn't found, returns false
  • list-separator($list) - Returns the separator of a list.

Map Functions

  • map-get($map, $key) - Returns the value in a map associated with the given key. If the map doesn't have such a key, returns null
  • map-merge($map1, $map2) - Merges two maps together into a new map. Keys in Map 2 will take precedence over keys in Map 1. Keys will return in the order they appear in Map 1 with additional keys from Map 2 appended to the end
  • map-keys($map) - Returns a list of all keys in the map
  • map-values($map) - Returns a list of all values in a map. This list may include duplicates if multiple keys have the same value.
  • map-has-key($map, $key) - Returns whether a map has a value associated with a given key

Mixins

Mixins are similar to functions, except that instead of return a single value, they return Sass

Mixins can be used at the root element or within selectors

The best mixins only write properties that change based on user input and don't needlessly duplicate properties

@mixin greeting($phrase, $place: 'Islands of Adventure', $color: #96360f) {
  content: $phrase + ' from ' + $place;
  background: $color;
  color: invert($color);
}

.hello--spanish {
  @include greeting('¡Hola');
}

.hello--french {
  @include greeting('Bonjour', 'Universal Studios', #1875b4);
}
.hello--spanish {
  content: "¡Hola from Islands of Adventure";
  background: #96360f;
  color: #69c9f0;
}

.hello--french {
  content: "Bonjour from Universal Studios";
  background: #1875b4;
  color: #e78a4b;
}
@mixin greeting($phrase, $place: 'Islands of Adventure') {
  $color: #96360f;
  content: $phrase + ' from ' + $place;
  background: $color;
  color: invert($color);
}

.hello--spanish {
  @include greeting('¡Hola');
}

.hello--french {
  @include greeting('Bonjour', 'Universal Studios');
}
.hello--spanish {
  content: "¡Hola from Islands of Adventure";
  background: #96360f;
  color: #69c9f0;
}

.hello--french {
  content: "Bonjour from Universal Studios";
  background: #96360f;
  color: #69c9f0;
}

Mixins can also take blocks of properties from a user

These blocks are called @content blocks

@mixin enhance-with($feature) {
  .#{$feature} & {
    @content;
  }
}

@mixin degrade-from($feature, $no-js: true) {
  .no-#{$feature} & {
    @content;
  }
}

.logo {
  @include enhance-with('svg') {
    background-image: url('../img/logo.svg');
  }

  @include degrade-from('svg') {
    background-image: url('../img/logo.png');
  }
}
.svg .logo {
  background-image: url("../img/logo.svg");
}
.no-svg .logo {
  background-image: url("../img/logo.png");
}

Challenge 07: Fancy Buttons

  • Add a primary and a secondary <button> to your HTML
  • Create and use a @mixin to style them. They should have a flat background color, 3px rounded corners, and a 2px outset border that changes to an inset border when active that's a 25% shade of the passed in color. The button should have a pointer cursor on hover. Their primary colors should be your primary and secondary color, respectively.
  • Remember! @mixin properties that are shared should not be duplicated!

Challenge 08: Color Contrasting

  • Update your @mixin so that if the color passed in is dark, the text color is #eee, and if it is light, the text color is #333, based on the YIQ color space
  • Hint! There may be a Compass extension you can use.

Challenge 09: Color Schemes

  • Put a link in your paragraph tag, make it your tertiary color
  • Using your chosen colors, create secondary-color and tertiary-color functions to automatically calculate your secondary and tertiary colors based on your defined primary color. This will then allow you to pass in any primary color and maintain your color relationships
  • Create a secondary stylesheet that has an alternative color scheme defined only by changing your primary color. Use the buttons to change between your primary color scheme and secondary color scheme. Rename them accordingly.
  • Hint! Take a look at the HSL color space

Sass Control Directives

Sass introduces a handful of control directives to make writing complex CSS easy

  • If/Else If/Else
  • For Loops
  • Each Loops

If/Else If/Else

If statements in Sass are very similar to if statements in any other language; @if a value evaluates to true, that code will fire.

Sass supports both and and or as well.

@mixin greeting($phrase) {
  @if $phrase == '¡Hola' {
    content: '#{$phrase} from Universal Studios';
  }
  @else if $phrase == 'Bonjour' {
    content: '#{$phrase} from Islands of Adventure';
  }
  @else {
    content: 'Wish You Were Here!';
  }
}

.hello--spanish {
  @include greeting('¡Hola');
}
.hello--french {
  @include greeting('Bonjour');
}
.hello--english {
  @include greeting('Hello');
}
.hello--spanish {
  content: "¡Hola from Universal Studios";
}

.hello--french {
  content: "Bonjour from Islands of Adventure";
}

.hello--english {
  content: 'Wish You Were Here!';
}

For Loops

The @for loop in Sass allows you to iterate over a series of things a given number of times

The basic syntax is @for 'counter' from 'start' to/through 'end' where 'counter' is a variable that will hold the current iteration count, 'start' is the number to start the counter at, and 'end' is the number to end the counter either at or before, depending on if you are going to or through the number.

@for $i from 1 through 6 {
  h#{$i} {
    font-size: 2em - ($i - 1) * .15em ;
  }
}
h1 {
  font-size: 2em;
}

h2 {
  font-size: 1.85em;
}

h3 {
  font-size: 1.7em;
}

h4 {
  font-size: 1.55em;
}

h5 {
  font-size: 1.4em;
}

h6 {
  font-size: 1.25em;
}

Each Loops

The @each loop in Sass allows you to iterate over either a list or a map of items and act on each individually.

The basic syntax is @each 'item' in 'listing' where 'item' is a variable to hold the current piece of the list and 'listing' is either a list or a map data type

$zoo: puma black, sea-slug green, egret brown, salamander red;

@each $animal in $zoo {
  .#{nth($animal, 1)}-icon {
    background-color: nth($animal, 2);
  }
}
.puma-icon {
  background-color: black;
}

.sea-slug-icon {
  background-color: green;
}

.egret-icon {
  background-color: brown;
}

.salamander-icon {
  background-color: red;
}

In Sass 3.3 and when using a map data type with your @each loop, you can write your loop to automatically give you the keys and values

The syntax for key/value pairs is @each 'key', 'value' in 'listing' where 'key' is a variable to hold the listing's key, 'value' is a variable to hold the key's value, and 'listing' is a map data type

$zoo: ("puma": black, "sea-slug": green, "egret": brown, "salamander": red);

@each $animal, $color in $zoo {
  .#{$animal}-icon {
    background-color: $color;
  }
}

Challenge 10: Color Schemes

  • Rewrite your secondary and tertiary color functions to be a single function that chooses which color based on its input.
  • Warn the user if they haven't chosen the secondary or tertiary ordinal

Challenge 11: Media Queries

  • Create a variable where you can store min-width media query values based on a human-readable name
  • Create a mixin that can take a human-readable name for a media query and a block of properties you'd like to use and write those properties in a media query
  • Choose 3 breakpoints, and change the color of the h1 from your primary color to your secondary color to your tertiary color
  • Make sure you're only writing a media query if a valid input is given to your mixin. Warn them if their input isn't valid.

Challenge 12: Icons

  • Add a list of links to your site, including GitHub, Reddit, Apple, Android, and Windows
  • Create a free icon from from icomoon.io/app of those URLS. The icons should apply based on a word, not a unicode character.
  • Add the correct icon next to any link that contains the icon's URL. Make it automatic so you don't need to apply classes to each link.
  • Make the background of each list item start with your tertiary color and get progressively lighter if it's a dark color, and progressively darker if it's a light color. Make sure your text is till legible and gets darker or lighter on hover.

Compass

As you've already seen, the Compass extension framework can be extremely powerful, but so is its Sass framework

  • Cross-Browser Compatibility
  • Image Helpers

Cross-Browser Compatibility

As of Compass 0.13, Compass's cross-browser compatibility prefixing is based on Can I use… data, with compatibility being determined by actual usage statistics

Prefixing is done through CSS3 mixins; each CSS3 property has a corresponding mixin provided by Compass

Prefixing can be done either through total worldwide percentage that needs it, or specific minimum browser support

// Browser usage threshold for features that gracefully degrade.
// Defaults to 1 in 1,000
$graceful-usage-threshold: 0.1;

// Browser usage threshold for features that cannot degrade gracefully.
// Defaults to 1 user in 10,000
$critical-usage-threshold: 0.01;

// Debugs Compass's reasoning for choosing browser support
$debug-browser-support: true;

@import "compass";

.foo {
    @include border-radius(5px);
}
.foo {
  /* Capability border-radius is prefixed with -moz because 0.42739% of users need it which is more than the threshold of 0.1%. */
  /* Creating new -moz context. */
  -moz-border-radius: 5px;
  /* Capability border-radius is not prefixed with -ms because 0% of users are affected which is less than the threshold of 0.1. */
  /* Capability border-radius is not prefixed with -o because 0% of users are affected which is less than the threshold of 0.1. */
  /* Capability border-radius is prefixed with -webkit because 0.19652% of users need it which is more than the threshold of 0.1%. */
  /* Creating new -webkit context. */
  -webkit-border-radius: 5px;
  border-radius: 5px;
}
// We are going to set minimum browser versions instead, so set to 1 to always print
$graceful-usage-threshold: 1;
$critical-usage-threshold: 1;

// Debugs Compass's reasoning for choosing browser support
$debug-browser-support: true;

// Minimum browser versions that must be supported
$browser-minimum-versions: (
  "chrome": "29",
  "firefox": "17",
  "android": "2.1"
);

@import "compass";

.foo {
    @include border-radius(5px);
}
.foo {
  /* Capability border-radius is not prefixed with -moz because 0.42739% of users are affected which is less than the threshold of 1. */
  /* Capability border-radius is not prefixed with -ms because 0% of users are affected which is less than the threshold of 1. */
  /* Capability border-radius is not prefixed with -o because 0% of users are affected which is less than the threshold of 1. */
  /* Capability border-radius is prefixed with -webkit because android "2.1" is required. */
  /* Creating new -webkit context. */
  -webkit-border-radius: 5px;
  border-radius: 5px;
}

Image Helpers

Compass also comes with a variety of image helper functions and functionality to assist in writing some fairly complex image related CSS a breeze

Because Compass knows where your images live (thanks to config.rb), it can provide simple helper functions like image-url() image-height(), image-width(), inline-image() all while pointing to relative image URLs

.background--universal {
  background-image: image-url('backgrounds/universal.jpg');
}

.icon--find {
  background-image: inline-image('icons/find.png');
  height: image-height('icons/find.png');
  width: image-width('icons/find.png');
}
.background--universal {
  background-image: url('../images/backgrounds/universal.jpg');
}

.icon--find {
  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUg…RK5CYII=');
  image-height: 25px;
  image-width: 25px;
}

Compass will also let you make image sprites automagically! Simply put a folder of .png files

Simply import your folder in your Compass project, and it'll automatically create a magic sprite for you! Change an individual file, and the whole sprite gets updated!

@import "icons/*.png";

.icon--find {
  @include icons-sprite('find');
}

.icon--minion {
  @include icons-sprite('minion');
}
.icons-sprite, .icon--find, .icon--minion {
  background-image: url('../img/icons-sf15a179063.png');
  background-repeat: no-repeat;
}

.icon--find {
  background-position: 0 0;
}

.icon--minion {
  background-position: 0 -25px;
}

Challenge 13: Cows

  • Download three cow pictures, put them in your images directory in a cows folder, make sure they're .png files, and create a sprite out of them
  • Make the sprites dynamic! The classes that they apply to should be drawn directly from the names of the files!
  • Make <div> sized to their respective background images on your page. Make them float next to each other.
  • Hint! Look up sprite-names()

Challenge 14: Flexbox

  • Choose two breakpoints to change the visual source order of your cow pictures when they're in line with each other
  • Using flexbox, rearrange their visual order so that the first ordering the cows are shifted one to the right, and the second ordering the cows are shifted on to the left

Challenge 15: Frameworks

  • Turn your partials directory into a personal Compass framework
  • Put your framework in a folder named framework at the root of your directory (so it's next to sass, not inside it).
  • Hint! See if you can override your color choice so you only need one color partial

Sourcemaps

Bonus!

Sourcemaps are the new Sass 3.3 way of debugging your compiled Sass files. They map the properties and values output in your CSS to where they come from, allowing for fine-grain debugging

At the time of this writing, the only stable support for Sass Sourcemaps is actually baked right in to Google Chrome Canary

Download Google Chrome Canary, open up the settings of Developer Tools, and enable CSS Source Maps, stop watching your files, and run this instead:

bundle exec sass --watch --compass --sourcemap sass:css

Open up your website, inspect an element, and CMD/CTRL click on a property or value to see where it comes from!

Happy Debugging!

Thank You

Slides available at

http://snugug.github.io/get-sassy