On Github elyseholladay / get-sassy
It's not mysterious or incomprehensible; it's programming, just of a different kind. You can and should make good architectural choices when you write CSS. In terms of modern languages, CSS is kind of unsophisticated, so it's easy to make a mess. As our sites and apps get bigger and more complex, our CSS has grown, but the way we write it may not have.
Thankfully some smart people wrote Sass, which helps us write systematic, thoughtful code, organize it well, and even make better docs! So!
/sas/
noun impudence; cheek; back talk.
verb being cheeky or rude to someone;to talk impudently to.
Not that...In the same way that it was mind-blowingly awesome that in CSS we could declare two classes to have the same styles, Sass allows us to abstract out CSS code into variables, mixins, and functions, even do math, if and while statements, etc. Like real programming!
Anyone in here already using Sass? looking into using it?
Sass* (the language) has two syntaxes
* it's not an acronym!
SCSS* or "Sassy CSS" looks just like CSS.Every .css file is a valid .scss file.
.widget { color: #fff; /* CSS works here */ background: $backgroundColor; /* but so do Sass variables */ margin: 20px; padding: 20px; }
* it's an acronym!
.sass syntax uses indentation rather than brackets to indicate nesting of selectors, and newlines rather than semicolons to separate properties.
.widget /* look ma, no brackets! */ color: #fff /* or semi-colons! */ background: $backgroundColor margin: 20px padding: 20px
.scss is a much easier place to begin, though, since it's a lot more forgiving and accepts all regular CSS.
I'm going to walk you through some of the basic functionality of Sass, first, so you can get an idea of what the language itself does. Then we'll talk about how to harness that power to switch over your projects.
$white: #ffffff; $grey: #cccccc; $lightpurple: #a18c9c; $darkpurple: #694160; $unit: 20px; $unitless: 10;Variables are a way to store a value, e.g. a color, number, string of text, or boolean, that you want to inject whereever that value would apply. It's easiest to understand with colors.
a, a:active, a:visited { color: $lightpurple; /* output: color: #a18c9c; */ padding-bottom: $unit/2; /* output: padding-bottom: 10px; */ font-size: $unitless+px; /* output: font-size: 20px; */ } a:hover, a:focus { color: $darkpurple; }
Everywhere I want #fff;, I can just write $white instead. It works with numbers, with or without units, too, and as you can see in this example, you can even do math on them.
@mixin box { color: $white; background: $darkgrey; padding: $unit/2; } /* including a Mixin */ .box { @include box; }
A mixin lets you make groups of CSS declarations that you want to reuse throughout your site. You can even pass in values to make your mixin more flexible.
This makes it easy for you to define a style (in this example, a box style) and include it in every div/class you want to have that style.
/* Output */ .box { color: #ffffff; background: #222222; padding: 10px; }
However, it does duplicate those lines every time, so if we used our box mixin twenty times, we'd have 60 lines of code.
Mixins are especially valuable for css3, like gradients, where you have many lines and don't want to write them all over and over. Browser-prefixes are a pain in the ass and mixins removes all that pain for you.
/* Mixin with a single argument */ @mixin border-radius-all($radius) { -webkit-border-radius: $radius; -moz-border-radius: $radius; -ms-border-radius: $radius; -o-border-radius: $radius; border-radius: $radius; } /* Mixin with multiple arguments */ @mixin border-radius($top-left, $top-right, $bottom-right, $bottom-left) { -webkit-border-radius: $top-left $top-right $bottom-right $bottom-left; -moz-border-radius: $top-left $top-right $bottom-right $bottom-left; -ms-border-radius: $top-left $top-right $bottom-right $bottom-left; -o-border-radius: $top-left $top-right $bottom-right $bottom-left; border-radius: $top-left $top-right $bottom-right $bottom-left; }
Mixins can have no arguments, a single argument, or multiple arguments - you create scoped variables for them and then in your include, put the values you want.
.box { @include box; @include border-radius-all(10px); } .button { @include border-radius(0, 2px, 0, 2px); }
.box { color: #ffffff; background: #222222; padding: 10px; -webkit-border-radius: 10px; -moz-border-radius: 10px; -ms-border-radius: 10px; -o-border-radius: 10px; border-radius: 10px; } .button { -webkit-border-radius: 0 2px 0 2px; -moz-border-radius: 0 2px 0 2px; -ms-border-radius: 0 2px 0 2px; -o-border-radius: 0 2px 0 2px; border-radius: 0 2px 0 2px; }
@extend is an easy way for one selector to share the styles of another selector, without duplicating the lines of CSS in your output.
.box { color: $white; background: $darkgrey; padding: $unit/2; } .widget { @extend .box; } .sprocket { @extend .box; }
.box, .widget, .sprocket { color: #ffffff; background: #222222; padding: 10px; }
Where mixins use @include and literally include the lines of code everywhere you write @include, @extend's output comma-delineates the classes. Instead the same 4 lines of CSS duplicated 3x, we have 4 lines.
http://codepen.io/elyseholladay/pen/CyEqsPlaceholder %extend takes this one step further, and eliminates the output for the original ruleset.
[read before slide] Sometimes you’ll write styles for a class that you only ever want to @extend, and never want to use directly in your HTML. For our box styles, if that's visual only and the class .box never appears in our HTML, why output it into our CSS if it will never get applied? If you use normal classes and use @extend for this, you end up creating a lot of extra CSS when the stylesheets are generated. [now read slide]
%box { color: $white; background: $darkgrey; padding: $unit/2; } .widget { @extend %box; } .sprocket { @extend %box; }
.widget, .sprocket { color: #ffffff; background: #222222; padding: 10px; }
So here you can see it's just removed class .box from the output. Not a major decrease here but if you do this a lot, then you will see much less clutter in your CSS. This is especially helpful for visual helpers, like typography classes, which I'll show you later.
Sass has standard math operators like +, -, *, /, and %
It also has equality operations == and != and relational operations > >= < <=.
+ can also be used to concatenate.
Sass has a handful of standard math operators like addition, subtraction, multiplication, division, and modulo (%). It also has equality operations, double equal == and not equal !=, and relational (greater than, less than, etc). Plus sign can also be used to concatenate.$unit: 20; $big: 40; .test { width: 100 + 200 - 20; /* addition, subtraction */ height: 20 % 2000 + em /* modulo */ margin: $unit*2+px /* unit variable, multiplication */ @if $unit == 20 /* if statement, equality */ padding: $unit+px /* + for concatenation */ @else if $big >= 40 /* else if statement, relational */ padding: $big+px } /* Output */ .test { width: 280; height: 20em; margin: 40px; padding: 20px; }
Sass also has @if statements, @for loops, and @each and @while controls.
As you can see in the previous example, Sass also has if, else if, for, each, and while control directives that allow you to write some pretty complex controls for your CSS.
If statements, etc typically used when building frameworks to allow for doing something like a responsive grid, where there's quite a bit of math involved.
$base-color: $black; .foo { @if $base-color == $black color: $white @else color: $black } /* Output */ .foo {color: white;}
Simple if statements in Sass test can test against variables. You can do multiple conditions, but something simple like this is what I have seen the most. I use this to test against a $responsive-test variable; if it is true, I display responsive layout grid backgrounds and border colors for ease of testing. That way, instead of commenting in/out a bunch of code, I just toggle one variable true or false.
lighten($color, $amount)
darken($color, $amount)
desaturate($color, $amount)
opacify($color, $amount)
transparentize($color, $amount)
rgba($color, $alpha)
ie-hex-str($color)
Sass has a ton of built-in functions. Here are some of the color-editing ones that are the most common: lighten, darken, desaturate, opacify (make more opaque by $amount), transparentize (make more transparent by $amount), rgba (change the alpha value of a color), and ie-hex-str, which converts a color to an IE friendly value (eg rgba to hex)
percentage($value)
type-of($value)
unit($number) & unitless($number)
round($value)
min($numbers…) & max ($numbers…)
Sass also has number functions, such as percentage, which converts a unitless number to a perctange; type-of returns the type of a value, and unit and unitless returns the unit associated with a number or if it has units, respectively. Sass can also do round, floor, min and max, etc.Media queries in Sass behave the same as in CSS, except they can be nested inside a CSS rule.
.sidebar { width: 300px; @media screen and (min-width: 1000px) { width: 500px; } }
.sidebar { width: 300px; } @media screen and (min-width: 1000px) { .sidebar { width: 500px; } }"@media directives in Sass behave just like they do in plain CSS, with one extra capability: they can be nested in CSS rules. If a @media directive appears within a CSS rule, it will be bubbled up to the top level of the stylesheet, putting all the selectors on the way inside the rule. This makes it easy to add media-specific styles without having to repeat selectors or break the flow of the stylesheet."
You can also name your breakpoints with a mixin...
@mixin breakpoint($point) { @if $point == large { @media (max-width: 1600px) { @content; } } @else if $point == medium { @media (max-width: 1250px) { @content; } } @else if $point == small { @media (max-width: 650px) { @content; } } }
This is another good example of a Sass if statement; here we are defining a mixin with an argument of $point, and then saying, if my $point value that gets passed in my include is large, then print out this mediaquery, and the @content of it inside it.
.page-wrap { width: 75%; /* all sizes fallback */ @include breakpoint(small) { width: 95%; } /* 0 to 650px */ @include breakpoint(medium) { width: 80%; } /* 650px to 1250px */ @include breakpoint(large) { width: 60%; } /* 1250px to 1600px */ }
Example stolen directly from Chris Coyier's CSS-tricks Named Media Queries article.
This is a much nicer way to author mediaqueries; it makes it easy to see what styles are related to what MQ size, you never have to write the #s again, or go searching through 2 different files to debug styles.
In CSS, @import allows you to import multiple stylesheets, but each stylesheet loads a separate HTTP request.
Sass @import can compile multiple Sass files, allowing you to use variables and mixins from other files, and only make one HTTP request.
[read slides]/* SCSS */ .about { margin: 20px auto; .column { float: left; width: 50%; } }
Last but not least, my least-favorite favorite Sass feature: nesting. You can nest a declaration inside another, and the output appends the classnames together.
/* Output — same as CSS */ .about { margin: 20px auto; } .about .column { float: left; width: 50%; }
This gets super helpful for namespacing modules—you can ensure in your ouput that the classes are namespaced inside a parent class, in this case .about, without having to literally write .about .column, .about .whatever over and over.
This can get really hairy if you do it too much, but used sparingly it's very cool. THINK before you nest: why would you NEED to? What does your output look like?
One enormous stylesheet?
Organization only via comments?
Bananas selectors?
No consistent naming strategy?
Unpredictable bugs?
Do you have one giant CSS file, organized only by comments? Is it clear where you should add a new section or code? Do you spend a lot of time searching through it? Bananas selectors with no consistent naming strategy? Is the cascade actually hurting you because you're inheriting styles you don't actually want?
How many times do you have !important in your CSS?
Funny cause it's true.. but Sass will help you make and stick to a plan that will make your life SO much easier.
Jina Bolton, in her Sass presentations, says "create systems, not pages."
How can you make a framework of code that is reusable and modular and separate from individual pages? Instead of "style the homepage", style the modules that are on the homepage.
Your code should be reusable in as many places as possible, which makes it more efficient to write and debug.
If anyone is familiar with SMACSS, here's a somewhat SMACSS-based setup for CSS project architecture.
Anything that has little to no CSS output but is required everywhere.
resets, themes, colors, typefaces and sizes, mixins (css3, clearfix), grid, grid or mediaquery plugins
Start with your variables, for colors, typefaces and sizes, helpers such as mixins for CSS3, reset CSS if required, any theming, and grid/mediaquery code or plugins such as Susy, Breakpoint, Compass, etc. any @import includes.
Core shared styles that map to the DOM; modules that can appear on any page and should always look the same.
header, footer, sidebar, forms and inputs, global avatar or profile styles, buttons, error messaging
[read slide]Individual page structure, fancy layout tricks, images, any one-off styles.
[read slide] Or if you don't have a lot of reusable modules, you may only have individual pages, like on your personal site it might be homepage, about, and contact pages, but the blog posts styles are really the only reusable module. That's OK too—solve for the problem you are presented with.gem install sass
.dashboard_tile .tile_content .tile_chart .tile_number.negative { color: red; }
* in the spirit of full disclosure, this is was—I fixed it!—from my code at work. please don't boo me off stage.
This is why you don't nest things in Sass unless they NEED to be. In this case, we really could eliminate the middle two classes—they aren't necessary. However I do want to nest at least the last 2 inside a parent, so that I can namespace and never worry about accidentally using that classname elsewhere and getting a conflict.
You get bloated output when you write bad CSS, when you nest when you don't need to, or when you over-use mixins or extends that aren't necessary. Read your output! YOU as an author are responsible for the output of your code, the exact same way as you are when you write plain CSS.
For nesting, follow the inception rule.
Also, in terms of performance and reducing size to the browser, you'll do FAR more by reducing your JS and images than you will reducing your CSS selectors.
Tom Genoni wrote a blog post about switching to Sass, in which he says that the best way to learn Sass without getting the insane output is to not use the crazy functionality in Sass.
"Sass is not a replacement for CSS, it’s more like having a CSS assistant who will help you write your code. So when you’re ready to really put it to work I recommend occasional sanity checks on the resulting CSS to see if this “assistant” has created too much repeated code or if the selectors are getting too complicated. Refactoring your Sass will help keep your code clean and you’ll start learning how you can make the best use of it."
I personally think using variables and mixins for obvious things you will reuse (like css3, clearfix, etc) is helpful, but if you are unsure about some of the extend or nesting or functions, don't use them--keep an eye on your code. This is an authoring tool and it's supposed to make your life BETTER, not harder.
http://atomeye.com/sass-and-compass.htmlthe next time you have to make a CSS change.
"I don't like the shade of blue we are using in all our buttons/links/headlines. Can we change it?"
"Ugh, this CSS file is a total clusterfuck."
"Why is this text 18px on one page, 20px on another, and 16px on another??"
I don't know about y'all, but I don't work on sites for clients where at the end of a client's site I can start the next one from scratch and use Sass next time.
I work on a product, an app, and I don't get to just delete my entire CSS folder and do it all over. So what about my legacy codebase? Switching to Sass is going to be HARD! I don't have time for a project that big!
It's ok! I'll walk you through it step by step, and I'm going to use some code from work as a little bit of an example.
Ok, but seriously, there's more to it than that — let's walk through some easy ways to actually harness the power of Sass in your project that STILL don't require starting from scratch.
The first thing you can change are things that don't actually change the structure of any of your CSS.
What reusable pieces can you pull out of your code that you know won't make anything break? Colors, type, and basic mixins are the easiest place to start.
cmd(+opt)+F in Sublime Text is your new BFF.
Color variables will make you more consistent both visually and in your CSS, and you'll never have to search for a hex color code in your CSS ever again.
Now is a really good time to take a little inventory of the UI of your app. What _should_ be white but is actually a super light grey? Do you have two VERY similar colors of blue? Can you consolidate some of your colors as you go to make things more efficient?
Here I'm using Sass' built in color operations to make a light grey that is just a lighter version of our dark grey, rather than defining a new hex value. I'm also using transparentize to make an rgba color for transparent text, rather than defining another rgba color.
Sass has a ton of these: lighten, darken, saturate, desaturate, invert, complement, adjust hue, alpha (returns alpha value), opacify/transparentize (not kidding, real names- makes more or less opaque), even "change-color" to change any property of a color: red, green, blue values, hue, saturation, lightness, and alpha.
http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html// TYPEFACE VARIABLES $fontHeadline: "Proxima Nova Bold", "Calibri", Arial, sans-serif; $fontBody: "Calluna", "Cambria", Georgia, sans-serif; $fontSecondary: "Proxima Nova Light", "Calibri", Arial, sans-serif;
Here I've defined a headline font, a body font, and a "secondary" font, in case we ever wanted to use that. Like colors, this allows us to change everything in only one place.
body { background: $white; font-family: $fontBody color: $greydark; } h1, h2, h3, h4, h5 { line-height: 1.2em; font-family: $fontHeadline; color: $highlight_color_bright; }
So here I am pointing our body font to $fontBody, and our headlines to $fontHeadline. Note that it's possible to point the $fontBody variable or vice versa in your variable declarations, if you only wanted one font. I'd still define 2 variables, and use them appropriately: for an h2, I'd use $fontHeadline; if it was in the body or a paragraph declaration, I'd use $fontBody. Even though $fontBody points to $fontHeadline, let's imagine change it later. If you used $fontHeadline in the body declaration, and changed the $fontBody variable, your body font wouldn't change.
Harry Roberts of CSS Wizardry wrote an article called "Pragmatic, practical font sizing in CSS" and in it he came up with this greek lettering method of naming font sizes. Instead of ONLY defining your font sizes on h1, h2, h3, etc, we also define it on classes alpha, beta, etc. "So now .alpha can carry the style information of a h1 wherever you wish; it doesn’t depend on location or a type of element."
I took that one step further with Sass, and used the new placeholder extend functionality. Now I don't have to have classnames in my HTML, but I can extend them in my Sass, like this:
.notification { &:hover { background: lighten($tertiary_color,25%); } // read state of a notification &.read { background: transparentize($tertiary_color,75%); } .icon_arrow_right { @extend %milli; color: $greylight; } .icon_notification { @extend %delta; color: $primary_color_bright; padding: 5px 10px 0 0; float: left; } .notification_title { padding: .75em .5em; border-bottom: 1px solid $greypale; @extend %zeta; } }
Just @extend %delta, and you'll get 18px, without having to put the classname in your HTML. Additionally, from the content in the typography.sass file, h4 is output, but %delta itself isn't. You could take that one step further and eliminate the h1-6 selectors and ONLY use the placeholders, if you wanted. This works really well for me at work, because in our complex app, we prefer to use unique classnames over HTML selectors, to eliminate cascade issues. For clarity, I also like to not have non-informational classnames in my HTML, like, .delta. If you are working on your own blog or something smaller, it probably isn't as necessary to either eliminate this much CSS output, or avoid the classnames, but whether or not you use placeholder or classes, this is still a very valuable organization technique.
You should be able to do this without really changing much of your output CSS. If you go through the places in your codebase that you have font-size declarations, the same as we did for color variables, and replace with @extend .delta (whether that's a class or placeholder), you'll get the exact same output. If you are adding line-height to your typography.scss file you might have to be a bit more careful. Now you also have consistency going forward.
.widget:after { content: ""; display: block; height: 0; clear: both; visibility: hidden; }
.clearfix:before, .clearfix:after { content: " "; display: table; *zoom: 1; /* For IE 6/7 only: Include this rule to trigger hasLayout and contain floats. */ } .clearfix:after { clear: both; }
// CLEARFIX // http://nicolasgallagher.com/micro-clearfix-hack/ // include @mixin clearfix on the parent div you want to clear @mixin clearfix { zoom: 1; // For IE 6/7 (trigger hasLayout) &:after, &:before { content: " "; display: table; } &:after { clear: both } }
An easy mixin to start with is the clearfix; you probably use it everywhere, and there's a few lines of code you can remove from your stylesheets that really add up.
Already this is MUCH easier to read!
If you regularly use gradients, rounded corners, transitions, box or text shadow, or other complex CSS3 declarations, abstract them out into mixins.
This is what some of mine look like at work! I have some with single arguments, some with multiple, and some with "variable arguments" which are the ones with the ... at the end, to allow for unknown numbers of variables.
At the top of your main SCSS file, @import your new color, type, and mixin files. The rest of the file is exactly the same as it was before.
Your site should not visually have changed at all—you just replaced some code with something easier for you to author!
Break out sections of your code that are related: errors, buttons, icons, sidebar, header, footer, etc.
Make a new file for each with a comment explaining what is in the file.
Goal is to keep files small and readable.
[read slide - 3 bullets]
Here we are still aiming to have no (or at least very little) visual change on the site. By doing this we just want to group our files, NOT start changing our CSS, classnames, etc.
Here's how I do it on my blog.
Group your new files into folders based on their type.
Your original CSS file should now be empty! Fill it back up by using @import to pull in all of your newly created files.
This is how I group mine at work, which is a bit more complex—and has better commenting!. There are lots of ways, and there's no limit to the number of folders you can have, as long as it makes sense for your app/site's files and CSS structure.
CodeKit will automagically compile all of this stuff for you as you save/change files. If you're running a Rails app, including the Sass gem will do this on page refresh, but not livereload.
So now that we have done that, our files are a bit easier to read, but what's in those files may still be clunky and messy. We'll still be getting the bugs we were before, have classnames that need to be cleaned up, etc. So let's talk about refactoring that actually changes your CSS, and maybe even HTML.
you have new requirements
the design or UI needs to change
code smell
bugs!?
[read slides]another great quote from jina bolton, in her realigning and refactoring talk. We refactor not to delete everything and start over, but to change the way the code is organized, change classnames and markup to be cleaner, without changing the end result.
We want our CSS to be clear, easy to read, easy to maintain; efficient to debug and to write, and to be DRY. Refactoring should improve upon one of those goals, if not all of them at once.
For example: if your CSS (sass or not!) matches your markup too much, you want to remove or consolidate your nesting or chained classes: refactor. If it may be cleaner and easier to use placeholder extend for a class you aren't using in HTML: refactor. If you have a bunch of classnames that should be a group but aren't: refactor.
Chris Eppstein, a Sass author, did a refactor of Digg's Feedback/voting stylesheet to see how they could benefit from Sass. This was in 2010—and he mentions a few features Sass didn't have then, like placeholder extend, that they do now that could improve on this even more.
link"I cut the related styles and pasted them into a new partial stylesheet named _feedback.scss. Then I inserted @import "feedback"; in its place in global.scss. Now I was able to focus on a single set of related styles."
/* This file started on line 2338 of global.css */ .confirm, .error, .warning, .warningPersistant, .info, .positive, .notice { color: #333; padding: 0.7em 5em 0.7em 4.3em; margin: 1.2em 0 1em 0 !important; clear: left; } /* ie6 */ .confirm, .positive, .notice { background: #eff6e8 url(/img/circle-check-green.gif) 1.3em 0.5em no-repeat; border-top: 1px solid #A5CC7A; border-bottom: 1px solid #A5CC7A; } .warning, .warningPersistant, { background: #fff url(/img/circle-yellow-exclamation.gif) 1.3em 0.5em no-repeat; border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; } .error { background: #fff url(/img/circle-red-exclamation.gif) 1.3em 0.5em no-repeat; border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; } .info { background: #fff url(/img/circle-yellow-info.gif) 1.3em 0.5em no-repeat; border-top: 1px solid #ddd; border-bottom: 1px solid #ddd; } .positive div, .confirm div, .error div, .warning div, .warningPersistant div, .info div .notice div { display: inline; background: none; padding: 0; margin: 0; } .positive p, .confirm p, .error p, .warning p, .warningPersistant p, .info p .notice p { margin: 0; } .positive h3, .positive strong, .confirm h3, .contirm strong, .warning h3, .warning strong, .warningPersistant h3, .warningPersistant strong, .error h3, .error strong, .info h3, .info strong { font-weight: bold !important; letter-spacing: normal !important; font-size: 1.2em !important; padding: 0 0.5em 0 0 !important; margin: 0 !important; display: inline; } .positive h3, .positive strong, .confirm h3, .contirm strong { color: #e18015; } .warning h3, .warning strong, .warningPersistant h3, .warningPersistant strong { color: #b50b05; } .error h3, .error strong { color: #b50b05; } .confirm:hover { color: #030; } h2 .confirm { font-size: 50%; float: right; } .instruction { background: #EAF2FA url(/img/feature-box.gif) 0 0 no-repeat; margin-bottom: 1em; color: #6C7D8E; } .instruction div { background: url(/img/feature-box.gif) 100% 100% no-repeat; padding: 15px 15px 10px 15px; } .instruction h3 { color: #6C7D8E; }
"...a large amount of selector duplication that implied an inheritance relationship, ...a repeating pattern relating to the colors and iconography, ...[and] some complex nesting of selectors."
"There is a lot of duplication in CSS when you use semantic markup. It can be challenging to identify the different types of duplication. First and foremost, I saw a large amount of selector duplication that implied an inheritance relationship. I looked for a CSS class that could function as the base class and found none. I also saw a repeating pattern relating to the colors and iconography. Lastly, there was some complex nesting of selectors."
"I decided to call it .feedback because all of the class names in use described a type of user feedback and this class was not in use already."
here is where he says "Unfortunately, Sass does not yet have any notion of an abstract base class, so this cleanup will incur some cost of additional output of a style they don’t want or need." which is exactly what placeholder extend is..feedback { color: #333; padding: 0.7em 5em 0.7em 4.3em; margin: 1.2em 0 1em 0 !important; clear: left; } .confirm, .positive, .notice { @extend .feedback; background: #eff6e8 url(/img/circle-check-green.gif) 1.3em 0.5em no-repeat; border-top: 1px solid #A5CC7A; border-bottom: 1px solid #A5CC7A; }
"Much of the duplication required when styling semantic content is due to styling nested content. ... I’m using Sass’s parent-reference selector & with the styles for h3, strong."
"Much of the duplication required when styling semantic content is due to styling nested content. Sass’s ability to nest selectors makes this stylesheet much easier to read and understand what’s going on. Note how I’m using Sass’s parent-reference selector (&) with the styles for h3, strong. The intent of that block is to style those elements, so I have inverted the nesting order to give more clarity to the intent of those styles."
h3, strong { .feedback & { /* Output is: .feedback h3, .feedback strong, etc */ font-weight: bold; letter-spacing: normal; font-size: 1.2em; padding: 0 0.5em 0 0; margin: 0; display: inline; } .positive &, .confirm & { color: #e18015; } .warning &, .warningPersistant & { color: #b50b05; } .error & { color: #b50b05; } } .confirm { &:hover { color: #030; } h2 & { font-size: 50%; float: right; } }
"The last major source of duplication ... is the common styling pattern for colors and iconography. To simplify this we extract a mixin and apply it wherever the pattern is in use."
@mixin feedback-appearance($bg-color, $icon, $border-color) { background: $bg-color url('/img/circle-#{$icon}.gif') 1.3em 0.5em no-repeat; border-top: 1px solid $border-color; border-bottom: 1px solid $border-color; } .confirm, .positive, .notice { @extend .feedback; @include feedback-appearance(#eff6e8, "check-green", #A5CC7A); } .warning, .warningPersistant { @extend .feedback; @include feedback-appearance(#fff, "yellow-exclamation", #ddd); } .error { @extend .feedback; @include feedback-appearance(#fff, "red-exclamation", #ddd); } .info { @extend .feedback; @include feedback-appearance(#fff, "yellow-info", #ddd); }
"But the biggest win is that adding a new kind of feedback requires only 1 or 2 points of edit instead of the 5-7 that would have been required before. This is, without a doubt, more maintainable...
Then he says,"Additionally, we fixed six bugs without trying." "the defect rate in this tested, in-production stylesheet is evidence of how hard it is for even great front-end developers to maintain semantic CSS."
Here we have an imaginary sampling of some buttons that you might see on a site. We have:
Save button a larger Sign Up button two edit buttons, one clearly disabled and slightly misaligned a delete button and a cancel link
Copy all the button-related CSS into a new file: buttons.scss, and include it in your main .scss file. Check to make sure everything is the same as it was.
What parts of our button CSS can we reuse?
body { font-family: "Pluto Sans", Arial; font-size: 100%; } a, a:hover, a:active, a:visited {text-decoration: none; font-weight: normal;} .save_button { display: inline-block; padding: 16px 22px; color: #fff; font-size: 24px; border-radius: 4px; text-transform: uppercase; color: #c6eff0; background-color: #2ba2a6; box-shadow: 0px 8px 0px 0px rgba(0,0,0, 0.125); border-right: 1px solid #217a7d; border-bottom: 1px solid #217a7d; text-shadow: 0px 1px 1px rgba(0,0,0,.25); } .save_button:hover { background: #217A7D; } .sign_up_button { margin-right: 10px; margin-left: 10px; background: #237cbe; box-shadow: 0px 8px 0px 0px rgba(35, 124, 190, 0.25); border: 1px solid #1b6093; text-shadow: 0px 1px 1px #0b283d; color: #fff; letter-spacing: 1px; font-size: 2em; line-height: 2.5em; padding: 20px; border-radius: 10px; text-transform: uppercase; } .sign_up_button:hover, .sign_up_button:focus, .sign_up_button:active { background: #1B6093; border-color: #0B283D; } .button-edit { display: inline-block; height: 65px; width: 110px; padding-left: 26px; padding-top: 16px; box-sizing: border-box; margin-right: 10px; text-transform: uppercase; font-size: 24px; border-radius: 4px; font-weight: bold; color: #d579cc; background-color: #993399; box-shadow: 0px 8px 0px 0px rgba(153, 51, 153, 0.25); border-right: 1px solid #732673; border-bottom: 1px solid #732673; text-shadow: 0px 1px 1px #260d26; } .button_edit:hover { background: #732673; } .button-disabled { display: inline-block; height: 65px; width: 110px; padding-left: 26px; padding-top: 18px; box-sizing: border-box; margin-right: 10px; text-transform: uppercase; font-size: 24px; border-radius: 4px; color: #888; background-color: #ccc; cursor: default; margin-right: 20px; } .button_delete { color: white; background-color: #e6475b; box-shadow: 0px 8px 0px 0px rgba(230, 71, 91, 0.25); border-right: 1px solid #dc1e36; border-bottom: 1px solid #dc1e36; text-shadow: 0px 1px 1px #821220; display: inline-block; padding: 16px 22px; border-radius: 4px; font-size: 22px; margin-right: 20px; } .cancel_button_link { color: black; font-weight: bold; font-size: .8em; text-transform: capitalize; } .cancel_button_link:hover { color: grey; }
This is the fun part! Audience participation time.. and go!
display: inline-block consistent padding and line height for height/width, not height/width or padding only box-shadow, text-shadow, border-radius CSS3 capitalization, using text-transform: uppercase and regular-case in HTML consistent hover/active/focus states consistent margin-right spacing consistent bold/unbolding.button { display: inline-block; box-sizing: border-box; margin: 0 20px 0 0; padding: .25em 1em; border-radius: 4px; font-family: "Pluto Sans", sans-serif; letter-spacing: 1px; font-size: 24px; line-height: 2.6; text-align: center; text-transform: uppercase; text-decoration: none; cursor: pointer; } /* Button Types */ .save_button { color: #fff; background-color: #2ba2a6; box-shadow: 0px 8px 0px 0px rgba(0,0,0, 0.125); border-right: 1px solid #217a7d; border-bottom: 1px solid #217a7d; text-shadow: 0px 1px 1px rgba(0,0,0,.25); } .save_button:hover { background: #217A7D; } .sign_up_button { background: #237cbe; box-shadow: 0px 8px 0px 0px rgba(35, 124, 190, 0.25); border: 1px solid #1b6093; text-shadow: 0px 1px 1px #0b283d; color: #fff; } .sign_up_button:hover, .sign_up_button:focus, .sign_up_button:active { background: #1B6093; border-color: #0B283D; } .button-edit { color: #d579cc; background-color: #993399; box-shadow: 0px 8px 0px 0px rgba(153, 51, 153, 0.25); border-right: 1px solid #732673; border-bottom: 1px solid #732673; text-shadow: 0px 1px 1px #260d26; } .button_edit:hover { background: #732673; } .button-disabled { color: #888; background-color: #ccc; cursor: default; } .button_delete { color: white; background-color: #e6475b; box-shadow: 0px 8px 0px 0px rgba(230, 71, 91, 0.25); border-right: 1px solid #dc1e36; border-bottom: 1px solid #dc1e36; text-shadow: 0px 1px 1px #821220; } .cancel_button_link { color: black; font-size: .8em; text-transform: capitalize; } .cancel_button_link:hover { color: grey; }
So here we get to start doing some Sass! Let's pull out all of the CSS that is shared across all of the buttons, and leave only what is unique to each button.
.button { display: inline-block; box-sizing: border-box; margin: 0 20px 0 0; padding: .25em 1em; border-radius: 4px; font-family: "Pluto Sans", sans-serif; letter-spacing: 1px; font-size: 24px; line-height: 2.6; text-align: center; text-transform: uppercase; text-decoration: none; cursor: pointer; } /* Button Types */ .save_button { @extend .button; ... } .sign_up_button { @extend .button; ... } .button-edit { @extend .button; ... } .button-disabled { @extend .button; ... } .button_delete { @extend .button; ... } .cancel_button_link { @extend .button; ... }so let's extend that button class into each of our button declarations.. voila.
.button, .save_button, .sign_up_button, .button-edit, .button-disabled, .button_delete, .cancel_button_link { display: inline-block; box-sizing: border-box; margin: 0 20px 0 0; padding: .25em 1em; border-radius: 4px; font-family: "Pluto Sans", sans-serif; letter-spacing: 1px; font-size: 24px; line-height: 2.6; text-align: center; text-transform: uppercase; text-decoration: none; cursor: pointer; }
.save_button { @extend .button; color: #fff; background-color: #2ba2a6; box-shadow: 0px 8px 0px 0px rgba(0,0,0, 0.125); border-right: 1px solid #217a7d; border-bottom: 1px solid #217a7d; text-shadow: 0px 1px 1px rgba(0,0,0,.25); &:hover, &:active, &:focus { background: #217A7D; } } .sign_up_button { @extend .button; background: #237cbe; box-shadow: 0px 8px 0px 0px rgba(35, 124, 190, 0.25); border: 1px solid #1b6093; text-shadow: 0px 1px 1px #0b283d; color: #fff; &:hover, &:active, &:focus { background: #1B6093; border-color: #0B283D; } } .button-edit { @extend .button; color: #d579cc; background-color: #993399; box-shadow: 0px 8px 0px 0px rgba(153, 51, 153, 0.25); border-right: 1px solid #732673; border-bottom: 1px solid #732673; text-shadow: 0px 1px 1px #260d26; &:hover, &:active, &:focus { background: #732673; } } .button-disabled { @extend .button; color: #888; background-color: #ccc; cursor: default; } .button_delete { @extend .button; color: white; background-color: #e6475b; box-shadow: 0px 8px 0px 0px rgba(230, 71, 91, 0.25); border-right: 1px solid #dc1e36; border-bottom: 1px solid #dc1e36; text-shadow: 0px 1px 1px #821220; &:hover, &:active, &:focus { background: darken(#e6475b,10%); } } .cancel_button_link { @extend .button; color: black; font-size: .8em; text-transform: capitalize; &:hover, &:active, &:focus { color: #666; } }I'm just going to nest the active/hover states, and make them consistent. Note that the disabled state doesn't get one--it doesn't need one! Now our hover/active/focus states are consistent and nested inside their parent button classes.
%button { display: inline-block; box-sizing: border-box; margin: 0 20px 0 0; padding: .25em 1em; border-radius: 4px; font-family: "Pluto Sans", sans-serif; letter-spacing: 1px; font-size: 24px; line-height: 2.6; text-align: center; text-transform: uppercase; text-decoration: none; cursor: pointer; } /* Button Types */ .save_button { @extend %button; // Placeholder color: #fff; background-color: #2ba2a6; box-shadow: 0px 8px 0px 0px rgba(0,0,0, 0.125); border-right: 1px solid #217a7d; border-bottom: 1px solid #217a7d; text-shadow: 0px 1px 1px rgba(0,0,0,.25); &:hover, &:active, &:focus { background: #217A7D; } } ...Next I want to change my .button class to a placeholder extend %button, so that it doesn't get output in my CSS. It doesn't eliminate many lines of code for us but since we don't want that class in our HTML anywhere, we can remove it from our CSS too.
.save_button, .sign_up_button, .button-edit, .button-disabled, .button_delete, .cancel_button_link { display: inline-block; box-sizing: border-box; margin: 0 20px 0 0; padding: .25em 1em; border-radius: 4px; font-family: "Pluto Sans", sans-serif; letter-spacing: 1px; font-size: 24px; line-height: 2.6; text-align: center; text-transform: uppercase; text-decoration: none; cursor: pointer; }Not a lot of difference here.. but .button isn't there anymore!
<a href="#" class="save_button">Save</a> <a href="#" class="sign_up_button">Sign Up!</a> <a href="#" class="button-edit">Edit</a> <a href="#" class="button-disabled">Edit</a> <a href="#" class="button_delete">DELETE</a> <a href="#" class="cancel_button_link">cancel</a>
This is the portion that involves editing your HTML. We could stop here: we have made a lot of good changes toward reusability and modularity, so even if the classes of the actual buttons are all inconsistent, we have done a lot. But some good search-and-replace can get us to a modular setup that will help in the future, when we want to add even more buttons.
<a href="#" class="button save_button">Save</a> <a href="#" class="button sign_up_button">Sign Up!</a> ...
.button {shared styles} &.save_button {unique styles} &.sign_up_button {unique styles}
Like I mentioned before, we could add button to our HTML and then nest all the unique button classes inside .button in our Sass file. But I don't like to do this—I like to remove classes.
<!-- .save_button is now: --> <a class="button_primary" id="button_save_form">Save</a> <!-- .sign_up_button is now: --> <a class="button_calltoaction" id="button_signup">Sign Up!</a> <!-- .button-edit is now: --> <a class="button_secondary" id="button_edit_profile">Edit</a> <!-- .button-disabled is now: --> <a class="button_secondary button_disabled" id="button_edit_profile">Edit</a> <!-- .button_delete is now: --> <a class="button_tertiary" id="button_delete_comment">Delete</a> <!-- .cancel_button_link is now: --> <a class="button_textonly" id="button_cancel_comment">Cancel</a>
What I like to do is use the stylistic class in addition to a unique ID. I prefer an ID because it allows us a unique way to call each button in JS, but also stick to a consistent naming scheme: button_buttonTYPE.
Then I apply the stylistic class. I use button_primary, secondary, tertiary, etc. You could also do like Bootstrap: default, primary, success, info, warning, danger, link. Very similar. They also go a bit more OOCSS style and do .btn-lg, sm, xs, to change text sizes.
// Disabled button state - can apply to other