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.
Sass is a language construct to create CSS
It makes writing awesome CSS easier
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 CSSTech reasons
Team reasons
Compass is a framework built around Sass, enhancing Sass in two main areas
The core of a Compass project is the config.rb
file, which lives at the root of your working directory:
Compiling with Compass can be done in one of two ways
compass compile
One-off compile of your Sass filescompass watch
Listener for changes to Sass files; will trigger a recompile with each changeSass 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
vendor
style.scss
in it<h1>
in index.html
red in your style.scss
filestylesheets
directory to css
, the javascripts
directory to js
, and the images
directory to img
Sass makes maintaining pre-compiled CSS easier by extending pre-compiled CSS in 3 useful ways:
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;
}
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;
}
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;
}
serif
and sans-serif
font stackprimary
, secondary
, and tertiary
colors, and make your <h1>
your primary color<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 colorcolors
, typography
, and extendables
partials inside of sass/partials
Sass has two main constructs to assist in writing CSS
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:
Select Color Functions
mix($color-1, $color-2, [$weight])
- Mixes two colors togethercomplement($color)
- Returns the complement of a colorinvert($color)
- Returns the inverse of a colorrgba($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 scaleNumber Functions
percentage($value)
- Converts a unitless number to a percentageabs($value)
- Returns the absolute value of a numberround($value), floor($value), ceil($value)
- Rounds a number to the nearest whole numbermin($numbers...), max($numbers...)
- Finds the min/max of several numbersString Functions
str-length($string)
- Returns the number of characters in a stringstr-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 stringstr-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 characterstr-slice($string, $start-at, [$end-at])
- Extracts a substring from the input stringto-upper-case($string), to-lower-case($string)
- Transforms string to upper/lower caseIntrospection & Miscellaneous Functions
type-of($value)
- Returns the primitive data type of the valueunit($number)
- Returns the unit(s) associated with a numberunitless($number)
- Returns whether a number has unitscomparable($number-1, $number-2)
- Returns wether two numbers can be addes, subtracted, or comparedif($condition, $if-true, $if-false)
- Returns one of two values , depending on whether or not the condition is trueList 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 spaceappend($list, $val, $separator)
- Appends a single value onto the end of a listzip($lists...)
- Combines several lists into a single multidimensional listindex($list, $value)
- Returns the position of a value within a list. If the value isn't found, returns falselist-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 nullmap-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 endmap-keys($map)
- Returns a list of all keys in the mapmap-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 keyMixins 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");
}
<button>
to your HTML@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.@mixin
properties that are shared should not be duplicated!@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 spacesecondary-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 relationshipsSass introduces a handful of control directives to make writing complex CSS easy
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!';
}
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;
}
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;
}
}
min-width
media query values based on a human-readable nameh1
from your primary color to your secondary color to your tertiary colorAs you've already seen, the Compass extension framework can be extremely powerful, but so is its Sass framework
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;
}
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;
}
cows
folder, make sure they're .png
files, and create a sprite out of them<div>
sized to their respective background images on your page. Make them float next to each other.sprite-names()
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 leftframework
at the root of your directory (so it's next to sass
, not inside it).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!
Slides available at
http://snugug.github.io/get-sassy