CSS has evolved and changed much since its initial release {[{'1996-12-17' | age}]} years ago. Its introduction ushered in a new era of separation of concerns, keeping document content and its presentation separate from one another. Since then we have enjoyed a myriad of advancements in the language as it tries to keep pace with the ever changing design landscape of the web. But there is one area of the language that hasn't changed, the very foundation at the core of CSS, the cascading nature of the language.
As we strive to build applications that are ever increasing in complexity, the need for encapsulation is now greater than ever.
In this article I will be looking at SUIT CSS, "a styling methodology for component-based UI development". I will be looking at the goals and the naming conventions of the methodology, the build and linting tools available, and above all else, why you should be adopting SUIT CSS on your projects today!
What is SUIT CSS?
At its core, SUIT CSS is a language design pattern for CSS. Its focus is towards the development of reusable component-driven UI elements, that are semantic and encapsulated by nature, and maintain a presentational relationship between the nodes represented in the DOM without coupling the presentation to a specific DOM structure.
Released under the an MIT licence by Nicholas Gallager in June 2014, SUIT CSS leverages the ideas first introduced in BEM (another web development methodology) in having semantically meaningful class names which are able to express how a component is structured, its presentation modifications, and its state.
Overview of SUIT CSS
.ComponentName {}
.ComponentName--modifierName {}
.ComponentName-descendentName {}
.ComponentName.is-stateOfComponent {}
.u-utilityName {}
What makes SUIT CSS really powerful are the subtleties that the language form semantically communicates. Lets break down each of these ideas:
Components
.ComponentName {}
Components in SUIT CSS are written in pascal case giving them a clear semantic separation from all other rule definitions. The class name will sit at the root node of the component itself.
<aside class="SideDrawer">...</aside>
If your library is going to be consumed by public projects, it is recommended that you prefix your component names with a namespace, written in lower case:
.namespace-ComponentName {}
Modifiers
.ComponentName--modifierName {}
Modifiers, denoted by a prefixed double dash (--), are written in camel case and form the suffix of the component to be modified, or the component descendent to be modified.
Their use is reserved for altering the base styles of a component in some form. They must always be used in addition to the base component class (multi-class pattern):
<button class="Button Button--primary">...</button>
Descendants
.ComponentName-descendentName {}
A component descendent is a class that is attached to a descendent node of a component. It's responsible for applying presentation directly to the descendent on behalf of the component. Descendent names must be written in camel case and are prefixed with a single dash (-).
<aside class="SideDrawer">
<a href="#" class="SideDrawer-link">...</a>
</aside>
Unless absolutely necessary, descendent classes should not communicate their depth in a component. This would otherwise couple the styles to a particular DOM structure, so you want to avoid the following:
/* Bad! */
.ComponentName-descendentName-anotherDescendent-andAnotherDescendent {}
/* Good! */
.ComponentName-andAnotherDescendent {}
Component State
.ComponentName.is-stateOfComponent {}
States are written in camel case as adjoining classes and should never be styled directly. This allows the same state names to be used in multiple contexts, but every component must define its own set of styles for that state, as they are scoped to the component.
<aside class="SideDrawer is-open">
<a href="#" class="SideDrawer-link is-active">...</a>
<a href="#" class="SideDrawer-link">...</a>
<a href="#" class="SideDrawer-link">...</a>
</aside>
Utilities
.u-utilityName {}
Utilities provide low-level structural and positional traits and can be applied to any element within a component. Utilities are prefixed with u-
and should focus typically achieving only one thing.
<img src="..." class="Post-img u-alignLeft" />
Why is this important?
So this is all well and good, but why should you care? While SUIT CSS conveys semantic meaning to your CSS, it is so much more than that. Because the structure of your CSS is largely flat and most importantly, is encapsulated, maintenance and refactoring either the HTML or the CSS becomes a breeze, enabling you to scale your application as much as necessary.
Specificity wars, infamous !important
overrides, and bleeding styles introduce so many defects and headaches in the long run, if your CSS has any of these problems, your architecture will be so much harder to maintain. Why put yourself through the pain?
SUIT CSS in practice
Let me elaborate a little with a more detailed example. Consider the following HTML:
<header class="Header">
<a href="#" class="Header-link">
<img src="..." class="Header-logo" />
</a>
<nav class="Nav Nav--topMenu Header-nav">
<a href="#" class="Nav-link is-active">...</a>
<a href="#" class="Nav-link">...</a>
</nav>
</header>
The .Nav
component keeps its styles entirely encapsulated from the .Header
component. The only class that creates a relationship between the two is the .Header-nav
descendent class, which would simply be acting on the .Nav
component in a positioning and/or structural manner.
If in the future we needed to refactor this code and detach the .Nav
component from the .Header
it would be as simple as moving the HTML out, and removing the .Header-nav
class.
All other styles should in reality remain largely unchanged due to how we have encapsulated our components.
Liberate your CSS
When I first started writing my styles in SUIT CSS parlance it felt strange, almost counter intuitive. I had become so used to inheriting styles, it was hard to realise the benefits. Until it came to refactoring, that's when it clicked.
Maintenance
What would have taken me hours of refactoring, and even longer to regression test, took minutes. I was able to make significant changes to the code base with 100% confidence that the only thing that was going to change was that particular component. It felt liberating. In the past CSS has felt like a house of cards, changing the smallest thing could cause the entire thing to come crashing down around you.
But what about code duplication? Computer science 101 tells us to always keep code DRY. Sure, with SUIT CSS there is potential for your code to become a little... erm, wetter, you could very well end up repeating yourself in a few places. But the wins you gain from a maintenance point of view far out weigh any negatives from code duplication in my opinion.
You can mitigate code duplication by using a pre-processor like SASS/SCSS, leveraging the @extend
statement and utilising @mixin
to keep your code DRYer. For example:
.Button {
...
}
.Button--primary {
...
}
.Dropdown {
@extend .Button, .Button--primary;
...
}
But what about the output size of the CSS? Won't it be much bigger/bloated? While performance should always be at the forefront of your mind when building applications, the output size of a CSS file shouldn't be the deciding factor to how you write the code in the first place. Minified file sizes do not tell the whole story, once gzipped the comparisons are no more than ~5% in difference in overall size. As Nicholas Gallagher mentions in his article 'About HTML semantics and front-end architecture':
CSS developers using pre-processors don’t need to be overly concerned about a certain degree of repetition in the compiled CSS because it can lend itself well to smaller file sizes after HTTP compression. The benefits of more maintainable “CSS” code via pre-processors should trump concerns about the aesthetics or size of the raw and minified output CSS.
And I'm inclined to agree with him. Having confidence in the code you're changing is key to a happy software cycle.
Testing
The other big win we gain from encapsulating CSS by using methodologies such as SUIT CSS, is testing. Components should be tested firstly in isolation from one another and the application context. Without encapsulation and decoupling of components, this is not possible. When composing components together in the larger application context, your end-to-end tests round out the remainder of testing.
If we go back to the .Header
/ .Nav
example above, where by we refactored the code to move the .Nav
component out of the .Header
, our tests around .Nav
should remain unchanged, our end-to-end tests may be the only place that require updating.
There is also available a Rework plugin for SUIT CSS to check component style isolation.
SUIT CSS Tools
While at its core SUIT CSS is a language design pattern, it also comes with a number of tools (all of which are entirely optional), that you can incorporate into your build process:
- CSS pre-processor - A future facing pre-processor which uses W3C working spec CSS variables and module definitions
- Style foundation - A base set of styles which sit on top of a thing layer of normalize.css (also by Nicholas Gallagher)
- Style utilities - A set of common utility classes
- Style components - A collection of high-level components
- Encapsulation testing - A standalone tool for testing CSS compliance against SUIT CSS methodology. Note: the SUIT CSS pre-processor includes this already.
Conclusion
Fundamentally, CSS wasn't originally designed for the types of applications we are building for the web today. Back then, the web was little more than a simple collection of web pages; table based layouts, animated marquees and the ever present under construction GIF. Who could of imagined we would be building applications for the browser, capable of streaming 4K video, audio, rendering entire 3D environments, dynamically populating the page with data from an array of input devices, such as GPS, compass, gyroscopes, web cams and more?
While the language has managed to keep pace with the changing design landscape and the advances in technology (just), at its core the cascading part of stylesheets creates more problems than it solves when building web applications, not just simple web pages.
In order to build high-quality web applications, comprised of many different components, then we simply have to have encapsulation in order to create a boundary between the code of that component and the code that will consume it. With correct presentation scope, the order in which components are loaded and composed no longer matters, and their styles do not bleed into other components of the application.
While the Shadow DOM will probably end up delivering this much needed encapsulation to CSS at a language level, we are still years off from being able to use it in production applications (at the time of writing only webkit enabled browsers; Chrome, Opera and Safari (excluding iOS Safari) support it).
Until then, language design methodologies such as SUIT CSS help to provide some sanity and structure to the scopeless madness. Paired with pre-compilers, linters and other build tools, today the suite of CSS tools are extremely powerful and useful, so that we are able to build complex, component-driven web applications that are able to scale today, instead of tomorrow.