On Github press22day / stylesheet-patterns
Stylesheet Design Patterns for Better CSS
Josh Renner / @joshrenner / josh@renner.mn
Made with Reveal.js<div id="main-container"> <div class="sidebar container"> <h1 class="title">Chippewa</h1> <!-- stuff --> </div> </div>
.sidebar .title { font-size: 1.2em; color: #333; } #main-container .title { color: #000; line-height: 1.6; } .sidebar { font-size: 1.1em; } .container h1 { color: #000; } .title { font-size: 2em; line-height: 1.4; }
Side effects, no clear hierarchy
.title { font-size: 2em; font-weight: bold; } .headline { font-size: 2em; font-weight: 900; } .hdln { font-size: 18px; font-weight: bold; } .head { font-size: 2em; font-weight: bold; }
multiple classes all solving the same problem. clear shared purpose and duplication
.widget { color: red; border: 1px solid black; width: 50%; } #sidebar .widget { width: 100%; } body.homepage .widget { border: none; }
Software entities should be open for extension, but closed for modification.
* { margin: 0; padding: 0; border: none; /* and it goes on */ } p, h6, h5, h4, h3, h2, h1, a, em, strong, li { font-variant: normal; font-weight: normal; font-style: normal; text-decoration: none; /* and it goes on */ }
.title { color: #000; } .main .title { color: #222; } .main > div .title { color: #000; }
#callout .title { color: #922; } #callout.muted .title { color: #644; } .main .title { color: #000 !important; }
.widget p { /* descendant */ } #chippewa { /* ids */ } h3.title { /* overly qualified */ }
.chippewa { margin: 1em !important; }
<div class="mod-accordion state-collapsed js-accordion-container x-placesToGoMenu"> <div class="wrapper js-accordion-title test-clickable"> <h4 class="title">Places to Go</h4> </div> <div class="wrapper js-accordion-content test-collapsible"> <ul class="mod-menu"> <li class="mod-menu-item"> <a class="test-link">Someplace</a> </li> </ul> </div> </div>
.widget { //elements .title {} .body {} > button {} //states &.collapsed {} //other variations .ie-8 & {} }
BEM [...] is a front-end naming methodology thought up by the guys at Yandex. It is a smart way of naming your CSS classes to give them more transparency and meaning to other developers. They are far more strict and informative, which makes the BEM naming convention ideal for teams of developers on larger projects that might last a while.
.block {} .site-search {}
Highest level of an abstraction or component.
.block__element {} .site-search__field {}
Descendant of a block that forms the block as a whole.
.block--modifier {} .site-search--full {}
Different state or version of a block.
The point of BEM is to tell other developers more about what a piece of markup is doing from its name alone. By reading some HTML with some classes in, you can see how – if at all – the chunks are related
At the very core of SMACSS is categorization. By categorizing CSS rules, we begin to see patterns and can define better practices around each of these patterns.
Basically, a CSS “object” is a repeating visual pattern, that can be abstracted into an independent snippet of HTML, CSS, and possibly JavaScript. That object can then be reused throughout a site.
<div class="widget-1 light"> <button class="button bright">push me</button> </div> <div class="widget-2 bright"> <button class="button">push me</button> </div>
.button { height: 50px; } .widget-1 { width: 500px; min-height: 200px; overflow: auto; .button { width: 90%; margin: 0 auto; } } .widget-2 { width: 50%; }
.light { border: solid 1px #ccc; background: linear-gradient(#ccc, #222); box-shadow: rgba(0, 0, 0, .5) 2px 2px 5px; } .bright { background: #00f; color: #eef; } .button.bright { border: 1px solid #009; }
With OOCSS, we’re encouraged to give more forethought to what is common among different elements, then separate those common features into modules, or objects, that can be reused anywhere.
Separating structure and skin can also mean using classes to name your objects and their components, rather than relying solely on the semantics of HTML.
Difficult to reuse these styles
.widget h3 { font-size: 14px; color: #333; padding-top: 10px; }
Better
.widget { padding-top: 10px; } h3 { font-size: 14px; color: #333; }
Best
.title { font-size: 14px; color: #333; }
Loosely, the single responsibility principle states that every module or chunk of code (a function etc) should do one job well and one job only. The benefits of this are mainly in the way of maintainability and extensibility.
Loosely, the single responsibility principle states that every module or chunk of code (a function etc) should do one job well and one job only. The benefits of this are mainly in the way of maintainability and extensibility.
it makes code too specific in its job to be portable and reusable
Abstracting chunks of functionality into several responsibilities means we can reuse a lot more of our code and recombine it over and over
A class of .wrapper is a good example of the SRP at play
<div class="island content"> <h2> <span>Buy now with promo code </span> <strong class="promo">0MG4WE50ME</strong> </h2> </div> <div class="island sub-content"> <a class="island promo">Buy now!</a> </div>
.island { display: block; padding: 20px; margin-bottom: 20px; } .promo { background-color: #09f; color: #fff; text-shadow: 0 0 1px rgba(0,0,0,0.25); border-radius: 4px; } .content { width: 640px; float: left; margin-right: 20px; } .sub-content { width: 280px; float: left; }
This one attribute holds everything, from enormous BEM-style names [...] to utilities [...], to JavaScript hooks [...], and so we spend a lot of time coming up with names that don't conflict with any others but are still vaguely readable.
.chippewa { /* styles */ } [class~="chippewa"] { /* styles */ }
<div am-Row> <div am-Column>Full</div> </div> <div am-Row> <div am-Column="1/3">Thirds</div> <div am-Column="1/3">Thirds</div> <div am-Column="1/3">Thirds</div> </div>
[am-Row] { /* max-width, clearfixes */ } [am-Column] { /* 100% width, floated */ } [am-Column~="1/12"] { /* 1/12th width */ } [am-Column~="1/6"] { /* 1/6th width */ } [am-Column~="1/4"] { /* 1/4th width */ } [am-Column~="1/3"] { /* 1/3rd width */ } [am-Column~="5/12"] { /* 5/12ths width */ }
Matching space separated attribute values since IE7. Works in the same way class selectors do, but with any attribute.
Attribute Modules, or AM, at its core is about defining namespaces for your styles to live in.
The presence of an attribute, e.g. am-Button, can and should be styled. The particular values of each attribute then alter and adapt these base styles.
CSS is still CSSSupersets are inclusive. Architecture can be.
Layers often impede before they payoffSkill sets and timelines may take precedence.
Scalability is not the main objective But scalability is not the only reason to choose a preprocessor...
The styles need to scale
The styles are distributed
.page { width: 100%; .widget { width: 50%; .button { width: 200px; } } }
.page { width: 100%; } .page .widget { width: 50%; } .page .widget .button { width: 200px; }
a { color: gray; &:hover { color: black; } } .block { width: 100px; .ie-6 & { width: 102px; } }
a { color: gray; } a:hover { color: black; } .block { width: 100px; } .ie-6 .block { width: 102px; }
.screencolor { @media screen { color: green; @media (min-width:768px) { color: red; } } @media tv { color: black; } }
@media screen { .screencolor { color: green; } } @media screen and (min-width: 768px) { .screencolor { color: red; } } @media tv { .screencolor { color: black; } }
$width: 200px; .widget1 { width: $width; }
.widget1 { width: 200px; }
$height: auto !default; .widget1 { height: $height; } .widget2 { $height: 100px; height: $height; }
.widget1 { height: auto; } .widget2 { height: 100px; }
#main { $width: 5em !global; width: $width; } #sidebar { width: $width; }
#main { width: 5em; } #sidebar { width: 5em; }
Mixins allow you to define styles that can be reused throughout the stylesheet.
@mixin color { color: blue; } .aMixed { @include color; width: 200px; } .anotherMixed { @include color; width: 100px; }
.aMixed { color: blue; width: 200px; } .anotherMixed { color: blue; width: 100px; }
@mixin color($color) { color: $color; } .aMixed { @include color(blue); } .anotherMixed { @include color(red); }
.aMixed { color: blue; } .anotherMixed { color: red; }
@mixin foo($width) { width: $width; } @mixin bar($color: blue) { color: $color; } .aMixed { @include foo; @include bar; } .anotherMixed { @include foo(200px); @include bar(red); }
.aMixed { color: blue; } .anotherMixed { width: 200px; color: red; }
@mixin foo($width: 100px, $height: 100px, $color: blue) { width: $width; height: $height; color: $color; } .foo { @include foo($color: red); }
.foo { width: 100px; height: 100px; color: red; }
@mixin apply-to-ie6-only { * html { @content; } } @include apply-to-ie6-only { #logo { background-image: url(/logo.gif); } }
* html #logo { background-image: url(/logo.gif); }
@function double($number) { @return $number * 2; } .foo { width: double(100px); }
.foo { width: 200px; }
p { width: 1in + 8pt; } p { font: 10px/8px; width: $width/2; height: (500px/2); margin-left: 5px + 8px/2px; }
p { width: 1.111in; } p { font: 10px/8px; width: 500px; height: 250px; margin-left: 9px; }
Number Relational Operators: < <= >= >
Relational Operators for all types: == !=
color: #010203 * 2
color: #020406;
$translucent-red: rgba(255, 0, 0, 0.5); color: opacify($translucent-red, 0.3); background-color: transparentize($translucent-red, 0.25);
color: rgba(255, 0, 0, 0.9); background-color: rgba(255, 0, 0, 0.25);
color: rgba(#f00, 0.8);
color: rgba(255,0,0,0.8);
ie-hex-str(green)
#FF00FF00
$text-size: 13px; $line-height: 1.3; $selector: "body"; #{$selector} { font: #{$text-size}/#{$line-height}; }
body { font: 13px/1.3; }
_text.scss
p { font-size: 1em; } h1 { font-size: 2em; } @mixin foo { font-family: Helvetica; }
common.scss
@import "text"; body { @include foo; }
common.css
p { font-size: 1em; } h1 { font-size: 2em; } body { font-family: Helvetica; }
%block { display: block; box-sizing: border-box; } .error { @extend %block; border: 1px #f00; } .seriousError { @extend .error; border-width: 3px; } .criticalError { @extend .seriousError; background: #fdd; }
.error, .seriousError, .criticalError { display: block; box-sizing: border-box; border: 1px #f00; } .seriousError, .criticalError { border-width: 3px; } .criticalError { background: #fdd; }
$fancy = 0; .foo { @if $fancy == 1 { border-style: solid; } @else if $fancy == 2 { border-style: dotted; } @else if $fancy == 3 { border-style: double; } @else { border-style: none; } }
.foo { border-style: #none; }
$buttons: 'facebook' 'twitter' 'linkedin' 'github'; @each $button in $buttons { .button-#{$button} { background: url("icons/#{$button}.png"); } }
.button-facebook { background: url(icons/facebook.png); } .button-twitter { background: url(icons/twitter.png); } .button-linkedin { background: url(icons/linkedin.png); } .button-github { background: url(icons/github.png); }
@import "base" =mixin ($arg: "default") &:before content: $arg .widget +mixin("chippewa") border: solid blue thin .title font-size: 2em @extend .headlines