It’s a weird, confusing, exciting time for CSS.
Over the past few years we’ve seen a proliferation of paradigms, concepts, architectures, libraries, and frameworks intended to assist us with writing modular, scalable, maintainable CSS. The inspiration for these things is commonly rooted in resolving specific issues the front-end community has had since the dawn of the web: coping with the lack of programmatic constructs (variables, control structures, scoping, etc.), alleviating the cognitive overhead (and reducing the cyclomatic complexity) caused by the cascade and issues with specificity, establishing sane, maintainable naming schemes, and above all else, architecting and authoring CSS in a way that more closely utilizes the excellent best practices we’ve seen emerge in other web languages over the past decade.
We’ve seen varying degrees of success across this set of bullet points:
- Preprocessors and abstractions like Sass, Less, and PostCSS to provide programmatic constructs and allow us to conceptualize CSS as a genuine, turing-complete programming language
- Patterns like Atomic/Functional/Utility to contribute to the flattening and simplification of selectors, as well as lead the movement back to a presentational naming scheme
- Architectures like ITCSS to assist us with organizing our code and files into logical chunks, ordered in a sensible, predictable manner to make tracing our source and knowing where to place new code a breeze
- Testing frameworks like True and Sassaby for unit testing abstracted CSS code
All of these things ride atop the success of what are now considered “mature” patterns and architectures in the realm of CSS: OOCSS, BEM, SMACSS, and the like.
Each of these things is wonderful in its own way, but given the specific foci of each solution, it quickly becomes clear that a wholly fleshed-out CSS architecture will be a cobbled-together Frankenstein monster consisting of a handful of these solutions — they may work wonderfully together, or they may contain overlapping conventions and opinions that make our lives more difficult and push our desires for clean, maintainable, scalable CSS even further from reach.
One of the most relieving developments in modern front-end development has been the move to componentized architecture — that is, thinking about and producing code in which markup, presentation, and business logic are bundled together structurally and/or functionally. We’ve seen this in the extreme popularity of React and the growing interest in web components (and web component-based frameworks like Polymer). The other major players in the JS framework space, such as Ember and Angular, have recognized the importance of this move and incorporated variants of the concept into their libraries as well.
This was a crux in the realm of modern front-end development, and we’re only beginning to see the extent to which this paradigm will be pushed.
It’s important to recognize the massive benefits we reap by using component-based architectures. Not only do components inherently lend themselves to concepts like code reuse and reusability; they also lend nicely to something even more important for us as developers: they reduce cognitive overhead, cyclomatic complexity, and the necessity that we constantly context-shift, tracing through any number of separate JS, HTML, and CSS files in order to specify dependencies, integrate markup/presentation/business logic, and debug.
It cannot be overstated how important this is. If you find yourself doubting the importance of an architecture in which the aforementioned reductions are accounted for, think back to the gnarliest Angular 1.x project you ever worked on. Recall having to trace through a labyrinth of HTML attributes, controller and directive names, filenames (often complete misnomers, given the controllers and directives they contain), module dependencies… Take a moment to close your eyes and visualize what the dependency graph of such an application might look like. I’d imagine “World’s Largest Ball of Twine” might be a suitable name for such a graph.
Now, given the rise of component-based architecture, particularly in the realm of JS, and the rampant popularity of component-based frameworks, we’re beginning to see similar concepts leak over into the CSS world. The community is champing at the bit for solutions that make our code easier to structure, easier to write, and easier to trace. Additionally, we’ve begun to tear down some of the preconceived notions we’ve had about CSS for decades — namely, the age-old mindset that HTML should never contain information about presentation.
Forward-thinking frameworks and libraries are riding this wave by utilizing patterns we mentioned above: the Atomic/Functional/Utility patterns that have grown in popularity over the past year or two. Tachyons, BassCSS, and my own framework, Nuclide, exemplify this.
Information can, of course, be found on the websites of each of the aforementioned projects’ websites, but to summarize: we’ll use a number of single-property CSS classes, all of the same minimal specificity, as simple, single-purpose building blocks with which to construct UI components. A contrived example of this via the Tachyons classes might look as such:
<section class="mw5 mw7-ns center bg-light-gray pa3 ph5-ns">
<!-- some content -->
Now, after you’ve overcome your disgust at the presence of so many presentational classes within HTML, let’s discuss the pros of this particular approach:
- Highly-declarative HTML means we can approximate what this element looks like without even having to look at our stylesheets
- Use of flat list of classes means we never have to be concerned about specificity wars or overrides
- Elimination of context-based CSS means we can properly plan and execute our architecture following modern best practices: modularity, reusability, and performance
- If we use this approach for the majority of our page elements, we can get by with an incredibly small amount of CSS across our site — just our reusable, composable Atomic classes (plus whatever gross, one-off stuff we have to write, as often happens on any given project)
Hey, these benefits are pretty great, right?!
Well, it’s not all peaches and cream. Let’s also consider the cons:
- Responsive design isn’t exactly a cakewalk given this model. Tachyons uses additional namespaced classes to indicate media query-enveloped classes — this effectively means we need to duplicate ALL of our Atomic classes for every breakpoint. Additionally, there’s cognitive overhead associated with having our base, mobile-first classes interspersed with our media queried classes; imagine having three or more breakpoints with a large number of stylistic changes on a given component
- Pseudo-classes can’t inherently be covered using this model. How would we implement hover or active states?
- Pseudo-elements can’t inherently be covered using this model. How would we implement styling on :before or :after elements?
Conceptually, it’s getting there. However, we can do better.
Shit’s about to get weird.
Let’s take this concept to its logical end.
We’ll design a framework for authoring CSS that leverages concepts we’ve seen success with in the JS world. Componentization is the key here, and it’s important that we understand that this is purely conceptual and spits directly in the face of how we’ve viewed HTML and CSS in the past.
We want our framework to meet the following criteria:
- Produces completely componentized HTML/CSS, bundling our content and presentation in a way that conceptually binds them
- Produces highly-declarative HTML such that we should be able to make some sense of what our component looks like without having to open any CSS files
- Gives us the capability of using media queries to implement responsive design, but preferably without needing duplicated CSS classes for every breakpoint
- Gives us the capability of applying styles to pseudo-classes
Almost immediately, we’re going to hit the limitations of HTML. A few of these things can’t be accomplished without using JS, so we’ll do just that.
Proposal: A JS library that will allow us to extend HTML attributes with a new set of attributes used specifically in conjunction with lists of Atomic CSS classes to achieve our goals. This library would function in a responsive, mobile-first manner, leveraging HTML’s native capabilities on underperforming or outdated browsers.
First, some sample HTML:
class="d-ib p-sm bgc-cool c-warm"
fxcss-device-desktop="d-b p-lg bgc-cloudy c-sunshine"
To explain what’s happening here: by default, the class attribute will be used to apply mobile-first styling. Then, on page load and/or the resize event, our library (fxCSS, for “futureCSS”) will pull in the custom attributes, applying the proper transformations per device. In this example, on a tablet device, fxCSS would use the combination of the -remove and -add attributes to remove the “p-sm” class and add “p-md” instead. Then, moving up to a desktop device, we’d replace the entire class list with the given desktop list (this functionality denoted by the absence of the -remove/-add directive). Lastly, we’d have an additional attribute for applying styling to pseudo-classes, such as hover.
Now, dealing with the fact that JS would be required to accomplish this functionality, there’s something really big happening here: by making our HTML 100% declarative, and by defining 100% of the CSS classes we’ll be using up front, we can do some pretty wild stuff:
- Reduce our CSS library on any given project to a widely-used framework that, say, takes in some configuration (specifying spacing units (padding/margin/widths/heights), fonts, colors, etc.) and spits out every conceivable utility class we could possibly need
- Implement a feature within fxCSS that would then pull in our HTML templates as input, scrape them for a 100% complete list of used CSS classes, and then pull in our compiled utility library, removing every single class we’re not using, resulting in a miniscule production CSS file (that contains no media queries, as the functionality to apply per-device styling rests within the JS lib, which could be selectively enabled or disabled)
- Allow us to write tests to ensure the proper classes exist on an element, as well as that the proper classes are removed/added on a per-device basis. We can take the concept of testing even further — since our classes now follow an established naming scheme and are single-responsibility, we could also perform functional checks — for example, if a child possesses “fl-l” (for float: left), we could check to see if the parent possesses “cf” (for a clearfix)
- Create a repository of HTML snippets for entire UI components — since the core CSS lib (and its naming conventions and generated classes) have (theoretically) become widely-used standards, HTML and HTML alone is now the source of truth for our components: 100% declarative and super easy to manipulate, if need be. Twenty different UI designers can now create twenty different variants of a navbar component, and since they all utilize the exact same core CSS classes, a simple copy-and-paste of an HTML snippet is all you need to test each one
There are, of course, some considerations:
- Performance. Given that these new attributes would be applied to virtually every element on a page, the performance hit could be substantial. There are potential accommodations — for example, on-demand transformations, where fxCSS would check and apply per-device styling only for what’s currently visible within the viewport, then traverse up and down the page, applying the styling for when the user scrolls
- Pseudo-elements still aren’t covered — other custom attributes might be a simple solution to such a problem
- Are there other concessions we need to make for less-capable browsers? In practice, is it possible we’ll hit limits like Internet Explorer’s CSS limitations?
- Would it be possible to hit the 80/20 mark on default configuration for the core CSS lib so that it could simply be referenced via CDN and actively writing/editing CSS effectively becomes a thing of the past?
Above all else, it’s clear that the way we think about CSS is changing dramatically. Front-end developers are now realizing that re-writing the same fifty properties ad nauseam isn’t a progressive or productive way to work. Additionally, the move to component-based architecture is forcing us to rethink how we build for the browser — why wouldn’t we bundle HTML and CSS together, when it’s such a natural pairing?
Let’s keep building, innovating, and challenging the way we think about how we’ve been building for the web.