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

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

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 few hundred web UIs in the world that are this complex by necessity

Ivory Towered Idealism?

Get in the f***ing sea!

Ending entropy

  • Easy maintenance regardless of size
  • Changes to one visual element should not inadvertently effect another
  • Simple decoupling of deprecated modules
  • Easy to iterate on existing designs
  • Should require 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

SMACSS

Didn't suit because:

  • Layout and grid styles not applicable
  • Wanted to avoid HTML element type selectors
  • Like OOCSS - no syntax 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 (Block Element Modifier) but didn't suit entirely because:

  • Modifiers are added to the root of a Block
  • Disliked the actual class naming syntax
  • Needed some way to encapsulate all eventualities
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
  • 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 proceeded by a hyphen
  • Component : Optional - upper camel case/pascal case and preceded by an underscore
  • variant : Optional - written all lowercase/train-case
"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. Let the tooling give the browser what it needs

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

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 queries 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

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
  • Aggressive minification
  • Linting

Autoprefixer

Autoprefixer remains the gold standard for abstracting auto-prefixing from your authoring environment

Use it!

Aggressive minification

cssnano is a modular CSS minifier. Use the features you want, switch off the ones you don't

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

Linting

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

JS config for misdemeanours that need policing

var stylelintConfig = { 
   "rules": {      
      "block-no-empty": 2,
      "color-no-invalid-hex": 2,
      "declaration-colon-space-after": [2, "always"],
      "function-comma-space-after": [2, "always"],
      "function-url-quotes": [2, "double"],
      "media-feature-name-no-vendor-prefix": 2,
      "number-leading-zero": [2, "never"],
      "number-no-trailing-zeros": 2,
      "property-no-vendor-prefix": 2,
      "rule-no-duplicate-properties": 2,
      "string-quotes": [2, "double"],
      "value-no-vendor-prefix": 2
   }
};

Configure it to fail your build

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

All sorted then?

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;
      }
   }
}

Clearly conventions can be 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

On a large team you have to go further

You need the real Fun Police!

Stylelint is extensible - so you can add your own 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

Remember:

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.

Don't be a sheep. Solve your own problems

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

Offer: $5 off for the next 48 hours
http://leanpub.com/enduringcss/c/slides

Web version soon at: http://ecss.io

Thanks!

One more thing

bet365 are hiring. Always happy to talk to talented developers

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