CSS for Engineers

Keith J. Grant

  • @keithjgrant
  • Intercontinental Exchange
  • CSS in Action, Manning Publications

CSS is Code

CSSpaghetti

Art & architecture

.tweet-container {
  padding: .5em 1em;
  font-size: 1em;
  color: #333;
  background-color: #eee;
  border: 1px solid #999;
  border-radius: .5em;
}

art

architecture

Code architecture

  • Understandable
  • Maintainable
  • Reusable
  • Scalable
  • Reduces/prevents bugs
  • Promotes sanity

Procedural -> OOP

"Compared with procedural programming,
a superficial examination of code written in both styles would reveal that object oriented code tends to be broken down into vast numbers of small pieces, with the hope that each piece
will be trivially verifiable"

https://en.wikibooks.org/wiki/Object_Oriented_Programming

"Procedural" CSS

#layout #header .page-title {
  color: black;
  font-size: 2em;
  margin-bottom: 1em;
}

.article #comments ul > li > a.button {
  border: 1px solid black;
  background: #369;
  color: #fff;
  padding: 5px 10px;
}

#main .header .left-side .title {
  font-size: 1.4em;
  color: #999;
  margin-bottom: 1em;
}

#main .header .right-side .title {
  font-size: 1.4em;
  color: #999;
}
  • Single-purpose
  • Repetitive
  • Tied to HTML structure

"OOP" CSS

.page-title {
  color: black;
  font-size: 2em;
  margin-bottom: 1em;
}

.button {
  border: 1px solid black;
  background: #369;
  color: #fff;
  padding: 5px 10px;
}

.side-title {
  font-size: 1.4em;
  color: #999;
}

.side-title--spaced }
  margin-bottom: 1em;
}
  • Reusable
  • Flexible
  • DRY
#layout #header .page-title {
  color: black;
  font-size: 2em;
  margin-bottom: 1em;
}

.article #comments ul > li > a.button {
  border: 1px solid black;
  background: #369;
  color: #fff;
  padding: 5px 10px;
}

#main #title .left-side .title {
  font-size: 1.4em;
  color: #999;
  margin-bottom: 1em;
}

#main #title .right-side .title }
  font-size: 1.4em;
  color: #999;
}
.page-title {
  color: black;
  font-size: 2em;
  margin-bottom: 1em;
}

.button {
  border: 1px solid black;
  background: #369;
  color: #fff;
  padding: 5px 10px;
}

.side-title {
  font-size: 1.4em;
  color: #999;
}

.side-title--spaced }
  margin-bottom: 1em;
}

Practical Methodologies

OOCSS
SMACSS
BEM

SMACSS:

Scalable and Modular
Architecture for CSS

Jonathan Snook
@snookca

https://smacss.com/

Categorize CSS rules

  • Base
  • Layout
  • Modules
  • State
  • Theme

Base rules

Set default values

body, form {
    margin: 0;
    padding: 0;
}

a {
    color: #039;
}

a:hover {
    color: #03F;
}
  • Tag selectors only
  • Includes browser resets (normalize.css)

Layout rules

High-level structure

#sidebar {
  float: left;
  width: 25%;
}

#main {
  float: right;
  width: 75%;
}

#footer {
  border-top: 1px solid #ccc;
  padding: 1em;
}
.l-sidebar {
  float: left;
  width: 25%;
}

.l-main {
  float: right;
  width: 75%;
}

.l-footer {
  border-top: 1px solid #ccc;
  padding: 1em;
}
  • Positions main regions of the page
  • Only 3-5 per page
  • Use ids or classes prefixed l-

Module rules

 

"Objects"

.media {
  display: table;
  margin-bottom: 1em;
}

.media > img {
  display: table-cell;
}

.media-body {
  display: table-cell;
  vertical-align: top;
  padding-left: 1em;
}

.media-title {
  text-transform: uppercase;
  font-weight: bold;
  margin-bottom: .667em;
}
  • Most of your CSS
  • One module per file (use a preprocessor to concat)
  • Avoid generic element selectors
  • Should work in any context
  • Keep them small—break up complex structures

Module rules

Discrete and reusable

.media {
  display: table;
  margin-bottom: 1em;
}

.media > img {
  display: table-cell;
}

.media-body {
  display: table-cell;
  vertical-align: top;
  padding-left: 1em;
}

.media-title {
  text-transform: uppercase;
  font-weight: bold;
  margin-bottom: .667em;
}
<div class="media">
  <img src="kitten.png"/>
  <div class="media-body">
    <h3 class="media-title">A kitten</h3>
    <p>Lorem ipsum dolor sit amet.</p>
  </div>
</div>

Module rules

Context independent

Module rules

Add variants with subclasses

.button {
  padding: .5em 1em;
  background-color: #369;
  color: white;
}
#sidebar .button {
  font-size: 1.2em;
}
.button { ... }

.button--large {
  font-size: 1.2em;
}
<button class="button button--large">

Suppose you need a different button in the sidebar...

State rules

Dynamic condition of a module

.section {
  background: #ccc;
  padding: 1em;
}

.section.is-collapsed {
  height: 0;
}
  • Apply to module base class (when possible)
  • Prefix class with is- or has-
  • Code lives with associated module
.navmenu { ... }

.navmenu > li.is-active { ... }

Theme rules

Add variant to entire site

// in module-name.css
.mod {
    border: 1px solid;
}

// in theme.css
.mod {
    border-color: blue;
}
  • Not necessary on most sites

Don't Repeat Yourself

Single source of truth

  • Information should exist once
  • Less to maintain
  • Makes changes simpler, less prone to error

General-purpose modules

#posts .post.partial h2 {
  font-size: 2em;
  font-weight: bold;
  margin-bottom: 1.2em;
}

.post.single h1 {
  font-size: 2em;
  font-weight: bold;
  margin-bottom: 1.2em;
}
#posts .post.partial h2,
.post.single h1 {
  font-size: 2em;
  font-weight: bold;
  margin-bottom: 1.2em;
}
#posts .post.partial h2,
.post.single h1,
#categories h2 {
  font-size: 2em;
  font-weight: bold;
  margin-bottom: 1.2em;
}
.post-title {
  font-size: 2em;
  font-weight: bold;
  margin-bottom: 1.2em;
}

What if you need that style again elsewhere?

.button-primary {
  padding: .5em 1em;
  border-radius: .5em;
  margin-bottom: .5em;
  background-color: #369;
  color: #fff;
}

.button-inverse {
  padding: .5em 1em;
  border-radius: .5em;
  margin-bottom: .5em;
  background-color: #fff;
  color: #369;
}

.button-danger {
  padding: .5em 1em;
  border-radius: .5em;
  margin-bottom: .5em;
  background-color: #e33;
  color: #fff;
}
.button {
  padding: .5em 1em;
  border-radius: .5em;
  margin-bottom: .5em;
}

.button--primary {
  background-color: #369;
  color: #fff;
}

.button--inverse {
  background-color: #fff;
  color: #369;
}

.button--danger {
  background-color: #e33;
  color: #fff;
}

Split into modifier classes

Utilize preprocessor

@mixin text-header-small {
  font-family: Georgia, serif;
  font-size: 1.1em;
  text-transform: uppercase;
}

.panel-title {
  @include text-header-small();
}

.modal-title {
  @include text-header-small();
}

.list-heading {
  @include text-header-small();
}
  • Sass/Less/PostCSS
  • Produces duplicate output
  • But source code maintains SST
  • @extend also good

Not all duplication is bad

.panel-body {
  background-color: #eee;
  font-size: 1em;
  margin-bottom: 1em;
}

.twitter-body {
  background-color: #eee;
  font-size: 1em;
  margin-bottom: 1em;
}
  • Sometimes values happen to be the same
  • Does not mean they are the same information
  • Does changing one value necessitate changing the other?
.panel-body {
  background-color: @gray-light;
  font-size: 1em;
  margin-bottom: 1em;
}

.twitter-body {
  background-color: @gray-light;
  font-size: 1em;
  margin-bottom: 1em;
}

Single Responsibility Principle

Single Responsibility

  • Every [module] should have responsibility over a single part of the functionality provided by the software
  • That responsibility should be entirely encapsulated by the [module]
  • All its services should be narrowly aligned with that responsibility

Do one thing

.button-register {
  display: inline-block;
  border-radius: .5em;
  font-size: 1.2em;
  padding: 1em;
  background-color: green;
  color: white;
}
.button {
  display: inline-block;
  border-radius: .5em;
}

.button--large {
  font-size: 1.2em;
  padding: 1em;
}

.button--success {
  background-color: green;
  color: white;
}
.button--large { ... }
.button--small { ... }
.button--success { ... }
.button--warning { ... }
.button--failure { ... }

And only one thing

.panel {
  padding: 1em;
  background-color: #ccc;
  border: 1px solid #999;
}

.panel-title {
  font-weight: bold;
  margin-bottom: 1em;
}

.panel-body {
  display: table;
}

.panel-body-left,
.panel-body-right {
  display: table-cell;
  width: 50%;
}

.panel-footer { ... }

.panel-button-close { ... }
.panel { ... }
.panel-title { ... }
.panel-body { ... }

.split {
  display: table;
}
.split-left,
.split-right {
  display: table-cell;
  width: 50%;
}

.button-row { ... }
.button--primary { ... }

"The first rule of classes is that they should
be small. The second rule of classes is that they should be smaller than that."

–Martin Fowler, Clean Code

Open/Closed Principle

Modules should be:

  • Open for extension
  • Closed for modification

Once a class is compiled and shipped,

it cannot be changed (except to fix bugs)

Closed for modification

.search {
  position: relative;
  margin-bottom: 1em;
}
#homepage .search {
  position: fixed;
  top: 2em;
  right: 2em;
  left: 2em;
}

Unsafe—this is modifying the original class

.search--fixed {
  position: fixed;
  top: 2em;
  right: 2em;
  left: 2em;
}

Safe—changes functionality through extension

Benefits:

  • Everything is explicitly opt-in
  • Styles don't accidentally apply elsewhere
  • CSS stays backwards-compatible with old markup

"Open for extension", but...

Favor Composition

over Inheritance

Break the problem
into smaller parts

  • Navbar
  • Flyout-drawer
  • Row/columns (grid system)
  • List-featured (w/ header)
  • Media-promo
  • Split
  • List-featured--inline
  • Button

Immutability

Immutability

  • Value cannot be changed once created
  • Not dependent state or conditions
  • Simple, predictable

Immutability

.dialog {
  padding: 1em;
  border: 1px solid #999;
}

.dialog > h3 {
  text-align: left;
}

.text-center {
  text-align: center;
}
<div class="dialog">
  <h3 class="text-center">
    Some title
  </h3>
</div>

Specificity mismatch!

.text-center {
  text-align: center !important;
}

!important is OK in

utility classes.

 

Really!

.button {
  padding: .5em 1em;
  background-color: #369;
  color: white;
  font-size: 1em;
}

#sidebar .button {
  font-size: 1.2em;
}
.button {
  padding: .5em 1em;
  background-color: #369;
  color: white;
  font-size: 1em;
}

.button--large {
  font-size: 1.2em;
}

If I haven't driven this
point home enough...

One class;
two outcomes

Two classes;
two outcomes

Inversion of Control

Inversion of Control

public class TextEditor {
    private SpellChecker checker;
    public TextEditor() {
        this.checker = new SpellChecker();
    }
}
public class TextEditor {
    private ISpellChecker checker;
    public TextEditor(ISpellChecker checker) {
        this.checker = checker;
    }
}

Logic is passed in. The calling code is in charge.

Core logic is buried inside the class. The class is in charge.

"IoC gives frameworks the power to serve as extensible skeletons. The methods supplied by the user tailor the generic algorithms defined in the framework for a particular application."

Johnson & Foote, Designing Reusable Classes

Benefits

  • Decouples dependencies
  • Makes code more reusable

"CSS First"

Your HTML is dependent upon your CSS.

Not the other way around.

When writing HTML first...

CSS conforms to HTML

.content div .connections > .wrapper ul > li.connection:hover > .presence {
  ...;
}

#sidebar ul.nav li:last-child {
  ...;
}

HTML is prone to change

CSS architecture cannot
depend upon it

Because immutability, HTML can depend upon CSS

Develop your CSS as if

it is for a third party

Documentation-driven

  • Working example of each CSS module
  • Build modules in standalone context
  • Promotes modules that work in any context 
  • Documentation is your unit test suite

Versioned

  • Use semver (1.0.1, etc.)
  • Publish changelog
  • Deprecate & delete features safely
    (dead-code elimination)
  • HTML opts-in to version of its choosing

OOCSS
SMACSS
BEM

Look for the MEAP!

@css_in_action

@keithjgrant

keithjgrant.com