By Matt Baker
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 functional programming principles is straight forward in most languages, even if they’re not functional by definition. 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. In this article I’ll cover some of the primary rules from our style guide.
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 other languages like Java scope 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, whether 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 by using a 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’ve insulated them 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 with 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 becomes full of surprises, and 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
. Any 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 overall 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 a Lovecraft-esque multi tentacled beast. With any luck, our “functional” approach will lend some inspiration as you develop your own.
This article was originally published at https://eng.wealthfront.com/2013/08/functional-css-fcss.html
“We’ve all seen css that looks like this”. Seen css like that? I’ve written it. Recently.
But you’ve done a brilliant job of explaining why I shouldn’t. Thanks very much.
Hey Matt,
Funny how great minds think alike. I basically had born something like this out of tremendous pain building enormously scalable responsive design projects. One thing I do take issue with however is the over use of @extend and the verbose naming convention. The problem that can arise is that you must make a completely unique name for each extended version. This can be quite a lot to track over time as well as it being completely dependent on how you author it ( for example you are using a layout block “side-bar” prepended). I prefer to use the original class as a baseline and use class chaining and ampersand (via SASS) for every different state, version, version within a state, etc etc.
This way I know that no matter WHERE I am, I can just start with “indicator” or “error” and burrow further with class chains as needed. Think of it as a adding to the “prototype” of an object rather than creating a new object every time. People are most familiar / can best understand a single object tree.
.indicator
width: 100px
height: 300px
// Default states
&:hover
background: lightred
&:focus
background: red
// Begin Class iterations
&.size-1
width: 150px
&:hover
background: lightblue
&:focus
background: blue
&.size-2
width: 200px
&:hover
background: lightgreen
&:focus
background: green
&.size-3
width: 250px
&:hover
background: lightorange
&:focus
background: orange
Thoughts?
Thanks, Jeff
Damn indenting got lost. Regardless I think its understandable based on my explanation. size-1,2,3 house different version of hover/focus basically. And all of it is nested under indicator.
While your intention is good with your “whitepaper” link example, your example is bad. Creating class names that describe the element’s appearance are also creating a dependency between the CSS and the HTML.
Well, CSS is about appearance, isn’t it?
Yes, it is, but HTML is not. Just as it is preferable to avoid enforcing DOM structure in CSS, it is preferable to avoid inserting pure styling information into HTML.
I somewhat agree. I did try to use this methodology for one project, and I did not like it at all. I ended up with classes like .smallerFonts .biggerFonts .invertedColors etc. and it was just an additional concern and additional things to memorize. Now I’d rather stick to classes for … classifying or characterizing repeatable elements (eg: item, wrapper, container, whatever….) (and ids for accessing unique elements). Using re-usable classes for visual characteristics sounded like a great idea but did not turn out that well. Maybe it’s just me but that’s my experience 🙂 I hope the author tried his solutions in development before writing about them.
See my comment below.
“A white paper is an authoritative report or guide helping readers understand an issue, solve a problem, or make a decision.[1] White papers are used in two main spheres: government and business-to-business marketing. They may be considered as grey literature.”
This was the traditional thinking, and I think it’s wrong. Here’s an approach that explicitly celebrates the decomposition of things like “sidebar” into “box gray-background 3-column offset-9”
https://coding.smashingmagazine.com/2011/12/12/an-introduction-to-object-oriented-css-oocss/
@Jason, the name “whitepaper”, in this context, has nothing to do with visual presentation.
“A white paper is an authoritative report or guide helping readers understand an issue, solve a problem, or make a decision.[1] White papers are used in two main spheres: government and business-to-business marketing. They may be considered as grey literature.”
—https://en.wikipedia.org/wiki/White_paper
Looks very similar to BEM: https://bem.info/
P.S. To site owners: use type=”email” in email input field.
I wrote something about faking a scope in CSS on Medium https://medium.com/front-end-development/681bda44c43e
When using @extend you really should be extending a placeholder class rather than a class itself. https://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#placeholders
This will help keep your CSS from becoming bloated and leading to conflicting rules.
Great tip! I didn’t know that was possible with SASS.
Hi, Matt
I liked your article. This is something I have recently been obsessed with and I would recommend you check out something called Inuit CSS (inuitcss.com) by a chap called Harry Roberts (@csswizardry). He built a framework for making maintainable and scalable CSS.
Great article!
Cheers
Tom
If you like Inuit, consider checking out Cascade Framework ( https://cascade-framework.com/ ). It has a similar architecture, is highly flexible and provides an optimal balance between features and footprint.
I agree with abstracting as much as possible and defining new class names so as to avoid the case where you don’t know how a class will react in any given context; however, I would avoid using classes that reference sizes (i.e. small, medium, large) as this can cause problems with responsive design.
In the same way you shouldn’t reference colours in classes, you shouldn’t reference sizes. Sure, you can create a mixin as a helper but this should never go into the markup.
perfect
Not the lispiest lisp around, but at least we’re making progress!
This is why I’m a fan of css frameworks like nocssti, it’s reusable, expandable, fast to learn and implement with. Great article. It’s true what you say about redefining. It’s horrible when it gets to the point where you override an !important with another !important.
If you get to that point then you should re-think and re-factor your CSS code.
!important should only be used for testing and some very rare cases. For all other cases, !important is not a tool, but an “emergency override”. You should think ahead and not rely on it.