Link to wealthfront.com

Fork me on GitHub

Tuesday, August 20, 2013

Functional CSS (FCSS)

We're big fans of functional programming at Wealthfront. Emphasizing immutability and functional style means fewer "surprises" because side-effects are limited or nonexistent. We can quickly build up large systems from discrete, focused components by way of composition.
Applying such principles is straight forward in most languages, even if they're not functional by definition, but the same can not be said of CSS. Let's look at some characteristics of our favorite (and most hated) styling language:
  • Everything is global scope.
  • Everything is mutable.
  • The precedence of definitions is calculated based on some interesting rules
So let's talk about what we can do. Wealthfront's CSS (really, SCSS) style guide outlines some rules-of-thumb that allow us to gain the benefits of the functional programming paradigm in CSS. Specifically, our guide helps us to minimize surprises by limiting side effects, and promotes composition so our CSS can more effectively scale.

Introducing scope to a scopeless language

In most languages, variables you define are limited to their scope. In Javascript, variables are scoped to their function, while languages like Java scopes them to the block. Variables I define or change inside my scope can't be re-defined or changed by someone else outside my scope.
Scope is an important part of programming defensively and minimizing side-effects. If your rule, function, or variable only exists in a limited scope then you can be assured no one will be altering it accidentally or intentionally.
CSS doesn't have scope. Style definitions can override each other accidentally, and there's no way to guarantee that the name you pick for your style rule won't be used by someone else. It could be in a completely different file, nested several selectors deep. If you pick a simple name for your style rule the odds that a fellow engineer will override it by accident are high. Let's consider this example:
/* profile.css */
.error { color: orange; }
.success { color: blue; }
/* signup.css */
.error { color: red; }
.success { color: green; }
If we included both css files in our HTML we'd be inadvertently overriding one style with another. As a site grows, these kinds of situations become increasingly more complicated and prevalent.
So how do we introduce scope to CSS?
The safest way to mimic a more granular scope in CSS is naming convention. In this case, we "namespace" all our style rules with a prefix. Namespacing rules with a prefix is not a new idea, but it's important that we understand why we're doing it. In prefixing our styles we create our "scope", you might say that our css is "prefix" scoped — our styles only exist within a given set of prefixed rules.
Let's try the example above with prefixing:
/* profile.css */
.profile-error { color: orange; }
.profile-success { color: blue; }
/* signup.css */
.signup-error { color: red; }
.signup-success { color: green; }
Prefixing allows us to encapsulate our rules and protect them from modification. With these prefixes attached, our rules won't step on each other's toes. We're insulated from side-effects by declaring our rules within the scope of our namespace.

Minimize dependencies, foster reusability

It's tempting to use complex selectors to keep our markup lean and free of classes. We've all seen css that looks like this:
.whitepaper-link {
  font-weight: bold;
  font-size:12px;
}

.main-nav .whitepaper-link {
  font-size:16px;
}

.main-footer .whitepaper-link {
  font-size:9px;
}
But what if we want to have our smaller .whitepaper-link somewhere else? By nesting selectors like this we enforce DOM structure in our styles. This says "you can only have a small whitepaper link if it's in a main-footer". Enforcing structure through CSS rules prevents us from reusing styles, and mixes the presentation of our data and its representation in markup. When we enforce structure by nesting selectors, we create a dependency between them. Managing dependencies in any area of software engineering is a headache and error prone. We should avoid it all costs.
Instead of enforcing a structure, let's define it like this instead.
.whitepaper-link {
  font-weight: bold;
  font-size:12px;
}

.whitepaper-link-large {
  font-size:16px;
}

.whitepaper-link-small {
  font-size:9px;
}
Our markup can add both the .whitepaper-link and .whitepaper-link-small class to the footer element to achieve the same effect as our old, nested styles. Now we can reuse the "small" style of our element anywhere in the site, whether or not it's inside a footer. What we're really seeing here is the power of composition, we'll talk about that more in a minute.

Avoiding mutability

Overriding style rules is not an uncommon process in CSS. For example, you may want an error message to appear differently if it's inside a sidebar container:
/* errors.css.scss */
.error { color: red; }
.sidebar .error { border:1px solid red; }
This is the stuff spaghetti code is made of. It's not unlike a group of engineers using a global variable. In some of their code certain engineers will redefine the variable (style), while others will expect it to retain its original definition. The .error style becomes unsafe to use, there's no way to know how it will behave in any given context. The style rule is full of surprises, we hate surprises.
The solution is to never override the definition of a style rule. If you treat rules as immutable, that is, they are set in stone and can never be changed after their definition, you can avoid a host of problems that arise out of global, mutable variables.
We can accomplish this by way of composition, whether we do it in the element's class attribute, or via Sass's @extend directive.

Composition is your friend

Let's look at how we'd use it to handle the example we described above.
/* errors.css.scss */
.error { color: red; }
.sidebar-error { border:1px solid red; }
\<!-- example.html -->
\<div class="error sidebar-error">Oh no!</div>
We don't redefine the .error rule, instead we attach a new rule to our error div that augments it. The appearance of our error div is the composition of .error and .sidebar-error.
This is still a little confusing, we don't override the .error rule itself, but we do override one of its properties. If you're using Sass, it's more expressive to define the composition of your styles in scss itself by way of the @extend directive.
/*errors.css.scss*/
.error { color: red; }
.sidebar-error { 
  @extend .error;
  border:1px solid red; 
}
example.html
<!-- example.html -->
<div class="sidebar-error">Oh no!</div>
Now our markup stays slim, and doesn't give the false impression that this should look exactly like an .error. Ay developer that looks at the errors stylesheet will see that .sidebar-error is an .error with an extra border. They can use .error with confidence since it will never be redefined, and we can still have our custom sidebar-error appearance.

FCSS

Wealthfront has a few more rules we didn't discuss in this article, but they all fit into the guiding principles we've discussed. For example, we avoid element and most pseudo selectors because they enforce DOM structure and we prefer classes over IDs to promote reusability (ID selectors are used for very specific, single-element overrides or styles).

To recap:

  • Namespace your classes with string prefixes to fake "scope" and minimize surprises
  • Don't override rules on a style with multiple definitions, use composition
  • Don't use nesting, element selectors, or excessive pseudo-selectors. They enforce DOM structure in your CSS


Every team needs its own style guide to enforce constraints a largely constraint-less language. Left to its own devices, ad-hoc CSS grows into Lovecraft-esque multi tentacled beast. With any luck, our "functional" approach will lend some inspiration as you develop your own.