Mastery Games

How to Tame the Cascade

Once upon a time there was a village named Cascadeville, named after the local dragon "Cascade". The people both loved and feared Cascade. This great beast protected the villagers against barbarian raiders, and fueled their furnaces with its great fire. But Cascade wasn't particularly careful where he walked or shot his flames, so the poor villagers didn't have a very long life expectancy. One day the people decided that something had to be done about this dragon.

They devised harnesses to control his fire, and a large dragon leash to keep him safely contained. The village thrived and the people lived happily ever after.

The Cascade Problem

Just like the dragon in our (true) story, the C in CSS (Cascading Style Sheets) is powerful but dangerous. Unless properly managed it can quickly turn an exciting new codebase into crumbling ashes. I've seen more than one project where even the bravest developers wouldn't dare risking a change to the CSS. And as soon as something can't be easily changed or deleted, it can't be maintained. This leads to countless hacks, one-offs, and workarounds getting heaped on top of the failing foundation.

What Exactly is the Cascade?

The fact that styles "cascade" means that the final styles applied to an element can come from many different sources:

  • user agent styles built into the browser
  • stylesheets
  • style tags
  • parent elements' styles
  • inline declarations
  • user/extension overrides

The browser runs a complex "cascade" algorithm to calculate exactly which style properties/values apply to each element. When there are duplicate properties (e.g. color) defined it takes into account the stylesheet load order, specificity, parent styles (only for some properties), style location (style tags win over stylesheets, inline wins over style tags), whether or not the !important bomb was dropped, etc. It's quite a lot to keep in your head while trying to build a layout.

Hey Who Broke my Stuff?

How many times have you been bitten by a CSS change made by another dev in a seemingly unrelated area of the project? This cascade problem is compounded by the use of global CSS names and well-meaning approaches that encourage code reuse. Building features and components that can have their styles impacted by other features and components is a brittle and error-prone way to work. Enough of getting burned by them flames!

The Cascade Solution

The cascade isn't going away anytime soon, but there are things we can do now to harness and isolate it, make it work for us instead of against us.

Reuse Components, not Styles

Most of us when we learn to code are taught that reusing code is a good thing. So we've been trying for over a decade to find a good way to reuse CSS. We even invented conventions like BEM, OOCSS, SMACSS, etc. to try to force it to work.

We had a lot of similar problems in JavaScript until some very smart people built us better tools. By using ES2015 import (or the webpack/rollup/browserify equivalent) we know exactly where everything in our app comes from thanks to explicit imports. It turns out this traceability is hugely important and lets us refactor JS code with much more confidence. But CSS cheats. It bypasses the explicit import contract, impacting your UI in strange and unpredictable ways.

The solution is to stop trying to reuse styles at all. When it comes to CSS, isolation is more important than reuse. Focus instead on building UI components that can be reused throughout your application. Make components your new building block. The component mindset is one of the biggest advances in the web community in the last 5 years. Build components that isolate their own styles, that don't use globals and that don't mess with anything outside of their boundry. Components that have to be explicitly imported to be used, so that you know what you'll impact when you edit/delete them. If your application is made up of a series of these strong components you'll avoid most of the cascade issues entirely, and have a codebase that is an asset rather than a liability.

How to Isolate Styles

Ok so how do we contain the dragon's flames (styles) to the component they belong to? There are a number of solutions that do the job for any of the popular JS frameworks. My friend Kent just released glamorous that I'm excited to check out. But my current favorite for React projects is styled-components. You build your component as a self-contained entity, styles and all. Here is my entire Zombie component:

import React from 'react';
import styled from 'styled-components';
import Widget from './widget';

function Zombie(props) {
  const ZombieStyle = styled.div`
    padding: 10px;
    width: ${this.props.width || 160}px;
    height: ${this.props.height || 308}px;
    z-index: 21;
  `

  return (
    <ZombieStyle>
      <Widget config={this.props.config} />
    </ZombieStyle>
  )
}

I'm able to reuse this component all over the place, with full confidence that it won't affect or be affected by other components in my project. All without having to think once about specificity, style order, or the cascade.

Other Advantages

In addition to pacifying the cascade dragon, styled-components gives us a neat bag of tricks.

Full Power of JS

Because it's using template literals I'm able to adapt the styles based on dynamic configuration passed in through the component's props. It also means you can have dynamic paths for things like background images. In my flexbox zombies project I have an ${assets} variable that gets set to /assets (local) during development, then the full Netlify CDN path in production.

Composability

Since styled-components are just components themselves, you can pass these style components around as props to other components, giving you a crazy level of composability.

One File Please

It's hard to describe how nice it is having your entire component in one place instead of three different (HTML/CSS/JS) files. Don't let the dated "separation of concerns" dogma keep you from this improved workflow.

Sane Way to Extend

You can also "extend" components, for those cases where you like the component you or a team member built but need a slight variation of it. This was possible in previous CSS approaches but was hard for the original author to know who is using/extending their component. With styled-components you can't cheat: to use or extend a component you have to import it explicity:

import Button from './ui/Button';

const ActionButton = styled(Button)`
  background-color: rgba(255, 142, 109);
  transition-property: border;
  
  &:hover{
    border-width: 5px;
  }
`

 

If you've been burned by CSS before I think you'll find this new approach removes a lot of pains. Time to show that old Cascade who is boss!