Enduring CSS

Architect and maintain large-scale CSS codebases

Ben Frain
http://benfrain.com / @benfrain

Who?

Senior Front-end developer at bet365.com

Author of the books:

What?

Enduring CSS a.k.a. ECSS is an approach to authoring and maintaining style sheets for rapidly changing, long-lived web projects

Many options for scaling CSS

I can't tell you ECSS is the way but it is a way

Tried and tested over a year by 20-30 developers

Styles a site generating a large portion of a £34 billion annual turnover

First wrote about ECSS in August 2014. Refining it ever since

What is ECSS good for?

  • ECSS is ideal for large-scale projects
  • But it isn't only for large-scale projects
  • It's for any project that might need to scale
  • Or change rapidly with ease
  • Many web projects evolve and mutate from their original intentions over time
  • ECSS facilitates that need

What is 'large-scale' CSS?

The term is quite nebulous. Could be:

  • Large file size, lots of rules
  • Large due to the interface the CSS represents
  • Large number of developers that touch the codebase

Large CSS codebases

  • Difficult to discern redundant code
  • Inconsistent levels of selector specificity
  • Visual concerns often spread throughout the entire codebase
  • Unneeded vendor prefixes
  • Poor commenting of hacks and workarounds

Medium.com The world isn't short of idealistic advice on how you should write CSS

But these approaches may not cater for the problems you have and the interfaces you need to build

Pragmatism > Idealism

Examples?

Can anyone give me an example of a complex web-based UI?

Complex UI

Responsive too

Unique Challenges

There are probably only a handful of web UIs in the world that are this complex by necessity

To exemplify; the notion that all buttons could inherit styles from a common ancestor is simply unrealistic

Ivory Towered Idealism?

Get in the sea!

Requirements

  • Easy maintenance regardless of size
  • Changes to one visual element should not inadvertently effect another
  • Simple decoupling of deprecated modules/features
  • Easy to iterate on existing designs
  • Consistent method of communicating state
  • Minimal tooling and workflow changes

CSS at scale

What's wrong with OOCSS, SMACSS & BEM?

Each solution is solving specific problems

That doesn't mean they are solving your problems

OOCSS

Didn't suit because:

  • Responsive Web Design
  • Rapid iteration

OOCSS (cont.)

Logical conclusion

Responsive: What happens when the abstraction needs to do something different in a different scenario?

Iteration: When an element needs changing, it'll then need two touch points; template and style sheets

Note!

Atomic/ACSS.io

  • I think it has a lot of merit
  • Now has some great tooling around it
  • Solves many of the issues I'm levelling at OOCSS
  • My concern was the portability of the styling
  • And continual developer education

SMACSS

Didn't suit because:

  • Layout and grid styles not applicable
  • Wanted to avoid HTML element type selectors
  • Like OOCSS - no authoring pattern to clearly define and encapsulate overrides to a key selector
  • But liked the declarative manner in which state was communicated:
.is-disabled {}

BEM

Much to take from BEM but didn't suit entirely:

  • Modifiers are added to the root of a Block
  • Needed some way to encapsulate all eventualities
  • Disliked the actual class naming syntax
  • Alternate syntaxes use -- which I associate with custom properties (and syntax highlighters can struggle with)
User
@User 14 minutes ago

So ??

Nothing quite solved the problems I had

"Good artists coders copy, great artists coders steal"
Pablo Picasso (sort of - sorry Pablo)

Introducing ECSS

  • Flat specificity of selectors
  • Self quarantining name-spaced components/modules
  • Encapsulated modules for consistent rendering
  • Simple decoupling of deprecated modules/features
  • Styles remain authored in CSS
  • A holistic approach to maintaining a CSS codebase

Flat specificity

  • It's not that IDs are bad
  • It's that we want an even playing field
  • Insisting on classes for selection buys us that
Selector Inline ID Class Type
.widget 0 0 1 0
aside#sidebar .widget 0 1 1 1
.class-on-sidebar .widget 0 0 2 0

ECSS naming convention

.ns-ModuleName_Component-variant {}
  • namespace : all lowercase/train-case. Can denote context or originating logic
  • Module : upper camel case/pascal case - always preceded by a hyphen
  • Component : Optional - upper camel case/pascal case and preceded by an underscore
  • variant : Optional - written all lowercase/train-case

The Result

"If you're having CSS problems I feel bad for you son, I got 99 problems but specificity ain't one."

Terminology

  • a module is the widest, visually identifiable, individual section of functionality
  • nested modules/sub-modules are the nested pieces of functionality that are included within a module
  • components are the individual parts that go to make up a sub-module (typically nodes in the DOM)

Example

(Alt+click to zoom)

Media object as ECSS

User
@BF 14 minutes ago

The micro-namespace provides a context so we could have an entirely different object in a different context; free to mutate as needed

ECSS is the antithesis of OOCSS. Rather than abstract we isolate

File organisation

We're used to grouping files by technology:

ECSS organisation

Instead we group by module:

Makes the removal of deprecated code far simpler

Maintainable projects stay lean

Easy code removal is important:

Two years ago I wrote a book where I was preaching DRY code, but after working on enduring projects (BBC News and theguardian.com responsive sites), it's "decoupling" that became more important to me. Say it with me: Kay Leeg Duh Loo Moh Pree Jon (silent N)

Dealing with states

All applications need to deal with states

A user does something:

  • Taps a button
  • Toggles a setting
  • Rotates their device

I liked how SMACSS handled states

is-Suspended

is-Live

is-Busy

But now we have ARIA

States re-done as ARIA

  • aria-disabled="true" (used instead of is-Suspended)
  • aria-live="polite" (used instead of is-Live)
  • aria-busy="true" (used instead of is-Busy)

Class handles aesthetics, aria-* handles state

Only just moving to using ARIA in this manner!

Authoring states in style sheets

.co-Button {
   background-color: $color-button-passive; 
   &[aria-live="polite"] {
      background-color: $color-button-selected;
   }
}

Caveats

Two touch-points for JavaScript: classes and attributes (rather than just one)

button.setAttribute("aria-disabled", "true");
Some older WebKit versions (e.g. Android stock browser 4.0.3) fail to recalculate styles on attribute changes. There are simple workarounds but it's something to be aware of

The Ten Commandments of Sane Style Sheets

Blessed are those that follow these rules for they shall inherit sane style sheets

Amen

ECSS uses PostCSS to enable a Sass-like syntax and facilitate additional tooling*

*More on tooling shortly

1

Thou shalt have a single source of truth for all key selectors

1


.key-Selector { 
   width: 100%;
   @media (min-width: $M) { 
      width: 50%;
   }
   .an-Override_Selector & {
      color: $color-grey-33;
   }
}

A key selector should only be found as the root of a rule once in the entire project: a single source of truth

2

Thou shalt not nest

Unless thou art nesting media queries, overrides or thou really hast to

3

Thou shalt not use id selectors

Even if thou thinkest thou hast to

An even specificity across rules allows for more manageable style sheets

4

Thou shalt not write vendor prefixes in the authoring style sheets

Autoprefixer means you can author styles as intended

5

Thou shalt use variables for sizing, colours and z-index

$size-full: 11px; 
$size-half: 5.5px;
$size-quarter: 2.75px;
$size-double: 22px;
$size-treble: 33px;
$size-quadruple: 44px;

Variables serve large projects well by normalising sizing and colours

Better to think of variables as const

6

Thou shalt always write rules mobile first (avoid max-width)

6

(continued)
.med-Video {
   position: relative;
   background-color: $color-black;
   font-size: $text13;
   line-height: $text15;
   /* At medium sizes we want to bump the text up */ 
   @media (min-width: $M) {
      font-size: $text15;
      line-height: $text18; 
   }
   /* Text and line height changes again at larger viewports */
   @media (min-width: $L) { 
      font-size: $text18; 
      line-height: 1;
   }
}

7

Use mixins sparingly (and avoid @extend)

7

(continued)
  • You shouldn't need more than 10 mixins in a project
  • Text truncation, scroll panels and font-stacks that are media query dependent are good candidates. Everything else is probably a needless abstraction
  • @extends offer minor KB savings when gzipped but add additional complexity

8

Thou shalt comment all magic numbers and browser hacks

Errant px values that aren't pre-defined in the variables should serve as a red flag

Any non-standard values or browser specific hacks should be commented

9

Thou shalt not place data URIs in the authoring style sheets

9

(continued)

Don't do this:

.rr-Outfit {
   min-height: $size-quadruple;
   background-image: url(data:image/gif;base64,iVBORw0KGgoAAAANSUh
   EUgAAABAAAAAQCAMAAAAoLQ9TAAAAA3NCSVQICAjb4UgAAAAw1BMVEX///8AAAC
   EhIQAAABYWFgAAAAAAAAAAAB6enomJiYAAAAiIiJ4eHgAAABiYmJgYGAAAACJiY
   kAAAC0tLR/f3/IyMjHx8e/v7+vr6+fn5+ZmZmUlJSBgYFmZmYiIiIQEBAAAAD//
   //w8PDv7+/i4uLh4eHf39/W1tbS0tLR0dHMzMzHx8fFxcXCwsK/v7+4uLi0tLSv
   r6+rq6ulpaWfn5+ZmZmQkJCPj498fHxwcHBgYGBAQEAwMDAgICAfHx8QEBAAAAA
   phWZmAAAAQXRSTlMAESIiMzNEVWZmZneIiKqqu8zM3d3u7u7u7u7u7u7u7u7///
   //////////////////////////////////////wP/q/8AAAAJcEhZcwAACxIAAA
   sSAdLdfvwAAAAVdEVYdENyZWF0aW9uIFRpbWUAMjEvNS8xMpVX8IQAAAAcdEVYd
   FNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzVxteM2AAAAw0lEQVQYlU2P6VLC
   UAyFD1KvWFQ2F1ChnLLW2oIs1ZZC8/5PRW4rM+RH5uSbLCeARt2YO2Pq+I+aafU
   AAAAAElFTkSuQmCC);
}

9

(continued)

Instead do this:

.rr-Outfit {
   min-height: $size-quadruple;
   background-image: inline("/path/to/relevant-image.png");
}

Lean on tooling where it can provide a better authoring experience

10

Thou shalt not write complicated CSS when simple CSS will work just as well

Only move as far as necessary from standard CSS; makes static analysis easier

Tooling

Ask anyone who works with me and they'll tell you I'm a tooling addict

PostCSS facilitates incredible tools for CSS

An ECSS approach benefits from a number of tools in the PostCSS eco-system

Game changers for CSS codebases

  • Autoprefixer
  • Linting

Autoprefixer

Autoprefixer remains the gold standard for automatically adding any needed vendor prefixes

Use it!

Linting

Stylelint is a Node based linting tool for the static analysis of style sheets

Example Stylelint Configuration

var stylelintConfig = { 
   "rules": {      
      "color-hex-case": "lower",
      "color-hex-length": "short",
      "color-named": "never",
      "color-no-invalid-hex": true,
      "font-family-name-quotes": "always-where-required",
      "font-weight-notation": "numeric",
      "function-comma-newline-before": "never-multi-line",
      "function-comma-newline-after": "never-multi-line",
      "function-comma-space-after": "always",
      "function-comma-space-before": "never",
      "function-linear-gradient-no-nonstandard-direction": true,
      "function-max-empty-lines": 0
   }
};

I keep my full config here

Configure it to fail your build

Over 150 rules!

Code QAs just got a whole lot easier!

Bonus: My esteemed colleague Peter Griffiths made a Sublime plugin to give you direct feedback in the editor

Bonus: Aggressive minification

cssnano is a modular CSS minifier. Use the features you want, switch off the ones you don't. Easy bolt-on to your PostCSS build

  • Convert lengths (16px => 1pt)
  • Rebase z-index (z-index: 1000 => z-index: 10)
  • Duplicate rule removal

Finished?

Devised a system, written extensive docs, briefed the team, added tooling

4 months later

.P2 .bb-ParticipantBettingBanner .ip-Participant_OppName {
      padding-left: 11px;
      margin-right: .5em;
      @media (min-width: 640px) {
      & .ip-Participant_OppName {
      display: inline-block;
   }
}
}

With many devs, conventions are difficult to enforce

Good will and documentation only get you so far

If there's a way to screw things up, people will find it

You need to continually refine your process

You need to be the Fun Police!

More rules!

Additional rules

  • Prevent key selectors from being compound (e.g. .ip-Selector.ip-Selector2 {})
  • Ensure key selectors are singular (e.g. .ip-Thing not .a-Parent .ip-Thing {})
  • Ensure only overrides and media queries can be nested (prevents nests that don't use a parent (&) selector)
  • Ensure that key selectors match ECSS naming conventions
  • Limit specificity and nesting levels

Yes, more!

  • prevent descending specificity
  • Disallow imperceptibly different colour values
  • Disallow type selectors
  • Ensure disabling Stylelint has a comment


The aim is for all style sheets to appear as if written by a single author

Implementation considerations

This is not particular to ECSS. But if you're about to start on a similar journey, I hope some of what follows may be useful

Prototype

Test and re-test the approach. Don't grab something 'off the shelf' and stick it into production

Document the failures

Ensure you document what didn't work

"Those who cannot remember the past are condemned to repeat it"
George Santayana

Adapt

Be ready to adapt. We started with utility styles (e.g. u-w50) but found they caused more problems than they solved

There are many ways to solve these problems. Evaluate approaches against your own requirements

Don't be a sheep!

There's lot's more in the book: http://leanpub.com/enduringcss

Offer: $5 off
http://leanpub.com/enduringcss/c/slides

Web version: http://ecss.io

Thanks!

Questions? @benfrain

Join us!

bet365 are hiring. Always happy to talk to talented developers

  • Based in Stoke On Trent
  • Industry leader
  • Use all sorts of exciting tech: JS, TypeScript, SVG, Gulp, Node etc
  • Email: jobs@bet365.com