Component Style

I think styling is one of the most interesting parts of the frontend stack lately. In trying to clearly outline a set of opinions about web development recently, I wanted to know if there was a “right” way to style components? Or, at least, given all the innovation in other parts of the stack (React, Angular2, Flux, module bundlers, etc. etc.), were there better tools and techniques for styling web pages? Turns out, the answer is, depending on who you ask, a resounding yes and a resounding no! But after surveying the landscape, I was drawn to one new approach in particular, called CSS Modules. Certain people seem to get extremely angry about this stuff, so I’m going to spend some time trying to explain myself.

First, some background on the component style debate.

If you wanted to identify an inflection point in the recent development of CSS thinking, you’d probably pick Christopher Chedeau’s “CSS in JS” talk from NationJS in November, 2014. — Glen Maddern
Slide from Christopher Chedeau’s presentation outlining problems with CSS at scale.

Chedeau’s presentation struck a nerve. He articulated 7 core problems with CSS, 5 of which required extensive tooling to solve, and 2 of which could not be solved with the language as it exists now. We’ve always known these things about CSS — the global namespace, the tendency towards bloat, the lack of any kind of dependency system — but as other parts of the frontend stack move forward, these problems have started to stand out. CSS is, as Mark Dalgleish points out, “a model clearly designed in the age of documents, now struggling to offer a sane working environment for today’s modern web applications.”

There has been a strong backlash to these ideas, and specifically to the solution proposed by Chedeau: inline styles in JavaScript. Some argue, that there’s nothing wrong with CSS, you’re just doing it wrong — learn to CSS, essentially.

If your stylesheets are well organized and written with best practices, there is no bi-directional dependency between them and the HTML. So we do not need to solve the same problem with our CSS that we had to solve with our markup. — Keith J. Grant

These critics also say there are more problems with the inline style approach than with properly written CSS. They point to naming schemes like OOCSS, SUIT, BEM, and SMACSS — developers love acronyms — as a completely sufficient solution to the global namespace problem. Others argue that these problems with CSS are specific to the type of codebase found at Facebook, “a codebase with hundreds of developers that are committing code everyday and where most of them are not front-end developers.”

The Modern Separation of Concerns

I think the real fight here is over separation of concerns, and what that actually means. We’ve fought for a long time, and for good reason, to define the concerns of a web page as:

  1. Content / Semantics / Markup
  2. Style / Presentation
  3. Behavior / Interaction

These concerns naturally map to the three languages of the browser: HTML, CSS, and JavaScript.

However, React has accelerated us down a path of collapsing Content and Behavior into one concern. Keith J. Grant explains this unified concern eloquently:

For far too long, we waved our hands and pretended we have a separation of concerns simply because our HTML is in one file and our JavaScript is in another. Not only did this avoid the problem, I think it actually made it worse, because we had to write more and more complicated code to try and abstract away this coupling.
This coupling is real, and it is unavoidable. We must bind event listeners to elements on the page. We must update elements on the page from our JavaScript. Our code must interact bidirectionally and in real-time with the elements of the DOM. If it doesn’t… then we just have static HTML. Think about it, can you just open up your HTML and change around class names or ids without breaking anything? Of course not. You have to pull up your scripts and see which of those you need to get a handle on various DOM nodes. Likewise, when you make changes to your JavaScript views, you inevitably need to make changes to the markup as well; add a class or id so you can target an element; wrap an extra div around a block so you can animate it a certain way. This is the very definition of tight coupling. You must have an intimate knowledge of both in order to safely make any substantive changes to either.
Instead, the mantra of React is to stop pretending the DOM and the JavaScript that controls it are separate concerns.

“Inliners” tend to argue that Style is also a part of that overarching concern, which can maybe be called Interface. The thinking is that component state is often tightly coupled to style when dealing with interaction. When you take input from the user and update the DOM in some way, you are almost always changing the appearance to reflect that input. For example, in a TodoList application, when the user clicks a Todo and marks it as completed, you must update the style in some way to indicate that that Todo has that completed state, whether it is grayed or crossed out. In this way, style can reflect state just as much as markup.

I think there is actually a second set of concerns being talked about here. This second set of concerns is more from the application architecture point of view, and it would be something like:

  1. Business Logic
  2. Data Management
  3. Presentation

On the web platform, the first two are handled by JavaScript, and a lot of the impulse towards inline styles is a move to get the whole presentation concern in one language too. There’s something nice about that idea.

The debate, though, is about whether or not style is a separate concern. This is, essentially, a debate over how tightly coupled style, markup, and interaction are:

Far too often, developers write their CSS and HTML together, with selectors that mimic the structure of the DOM. If one changes, then the other must change with it: a tight coupling.
If your stylesheets are well organized and written with best practices, there is no bi-directional dependency between them and the HTML. So we do not need to solve the same problem with our CSS that we had to solve with our markup.
~Keith J. Grant

Clear Fix

At this point, you should have a decent understanding of the different arguments over component style. I’m now going to outline my own opinions and highlight the approach that has emerged from them.

The foundational issue with CSS is fear of change

Recall the definition for tight coupling Grant offered:

You must have an intimate knowledge of both in order to safely make any substantive changes to either.

I think, ultimately, there are ways to organize and structure your CSS to minimize the coupling between markup and style. I think smart designers and developers have come up with approaches to styling — naming schemes, libraries like Atomic and Tachyons, and style guides — that empower them to make substantive changes to style and content in separation. And I respect the unease with the seemingly knee-jerk “Everything should be JavaScript!” reaction that seems to appear in every conversation lately.

That said, I like to minimize cognitive effort whenever possible. Things like naming strategies require a lot of discipline and “are hard to enforce and easy to break.” I have repeatedly seen that CSS codebases tend to grow and become unmanageable over time:

Smart people on great teams cede to the fact that they are afraid of their own CSS. You can’t just delete things as it’s so hard to know if it’s absolutely safe to do that. So, they don’t, they only add. I’ve seen a graph charting the size of production CSS over five years show that size grow steadily, despite the company’s focus on performance.
~ Chris Coyier

The biggest problem I see with styling is our inability to “make changes to our CSS with confidence that we’re not accidentally affecting elements elsewhere in the page.” Jonathan Snook, creator of the aforementioned SMACSS, recently highlighted this as the goal of component architecture:

This component-based approach is…trying to modularize an interface. Part of that modularization is the ability to isolate a component: to make it independent of the rest of the interface.
Making a component independent requires taking a chunk of HTML along with the CSS and JavaScript to make it work and hoping that other CSS and JavaScript don’t mess with what you built and that your component does not mess with other components.

I want to get rid of that fear of change that seems to inevitably emerge over time as a codebase matures.

JavaScript is not a substitute for CSS

There are a lot of very good arguments for sticking with CSS. It’s well-supported by browsers; it caches on the client; you can hire for it; many people know it really well; and it has things like media queries and hover states built in so you’re not re-inventing the wheel.

JavaScript is not going to replace CSS on the web — it is and will remain a niche option for a special use case. Adopting a styles-in-JavaScript solution limits the pool of potential contributors to your application. If you are working on an all-developer team on a project with massive scalability requirements (a.k.a. Facebook), it might make sense. But, in general, I am uncomfortable leaving CSS behind.

A Stylish Compromise

I’ve found CSS Modules to be an elegant solution that solves the problems I’ve seen with component styling while keeping the good parts of CSS. I think it brings just enough styling into JavaScript to address that fear of change, while allowing you to write familiar CSS the rest of the time. It does this by tightly coupling CSS classes to a component. Instead of keeping everything in CSS, e.g.

/* component.css */
.component-default {
padding: 5px;
}
/* widget.css */
.widget-component-default {
color: 'black';
}
// widget.js
<div className="component-default widget-component-default"></div>

or putting everything in JavaScript, e.g.

// widget.js
var styles = {
color: 'black',
padding: 5,
};
<div style={styles}></div>

it puts the CSS classes in JavaScript, e.g.

/* component.css */
.default {
padding: 5px;
}
/* widget.css */
.default {
composes: default from 'component.css';
color: black;
}
// widget.js
import classNames from 'widget.css';
<div className={classNames.default}></div>

Moving CSS classes into JavaScript like this gets you a lot of nice things that might not be immediately apparent:

  • You only include CSS classes that are actually used because your build system / module bundler can statically analyze your style dependencies.
  • You don’t worry about the global namespace. The class “default” in the example above is turned into something guaranteed to be globally unique. It treats CSS files as isolated namespaces.
  • You can still create re-usable CSS via the composition feature. Composition seems to be the preferred way to think about combining components, objects, and functions lately — it makes a lot of sense for combining styles too, and encourages modular but readable and maintainable CSS.
  • You are more confident of exactly where given styles apply. With this approach, CSS classes are tightly coupled to components. By making CSS classes effortless to come up with, it discourages more roundabout selectors (e.g. .profile-list > ul ~ h1 or whatever). All of this makes it easier to search for and identify the places in your code that use a given set of styles.
  • You are still just writing CSS most of the time! The only two unfamiliar things to CSS people are 1) the syntax for applying classes to a given element, which is pretty simple and intuitive, and 2) the composition feature, which will probably feel familiar to people who’ve been using a preprocessor like SASS anyway.

I’ve found that I can focus more on the specific problem I’m solving — styling a component — and I don’t have to worry about unrelated things like whether a CSS class conflicts with another one somewhere in another part of the app that I’m not thinking about right now. My CSS is effortlessly simple, readable, and concise.

CSS Modules is still a nascent technology, so you should probably exercise all the usual caution you would around things like that. For example, I found this behavior/bug to be particularly frustrating, and I expect I’ll run into more things like that. But I see them as rough edges on a very sound and promising model for styling components.

A lot of the most exciting stuff with CSS Modules directly relates to module bundling and the modern approach to builds — a topic for a future post perhaps.

I obviously read, linked, and quoted articles and talks by many other folks here. Thanks to Glen Maddern, Christopher Chedeau, Mark Dalgleish, Keith J. Grant, Michael Chan, Chris Coyier, Joey Baker, Jeremy Keith, Eric Elliott, Jonathan Snook, Harry Roberts, and everyone else who is thinking about this stuff in thoughtful and caring ways!