How I organize CSS in large projects using UFOCSS - Part 1
Diving into Abstracts and Modules
Working on big projects means facing up the challenge of working on a big codebase in a large team. Too many times I found myself not respecting the main principles of software engineering, like DRY (Don’t repeat yourself), KISS (Keep it Simple Stupid) and YAGNI (You aren’t gonna need it)
With these issues in mind, I started approaching the most commonly used CSS architecture systems: OOCSS, SMACSS, ITCSS, ACSS and BEM. I’ve already talked about my experience with them in my previous article State of the art in CSS: a closer look at CSS architecture systems.
Actually, every CSS architecture is valuable to me for some reasons and I really don’t care to look for the best. I’ve simply concluded that the best solution is just the one that really works for all the people working on it. What generally ends up being a weakness is far from being a human-friendly solution. Sometimes we easily get lost in technicalities and forget that there’s a person behind each line of code. Rather, the way we write and organize the code should be an important vehicle to convey useful information to other developers, not only a technical solution to a problem.
This implies for me that setting a convention is not enough anymore. We also need to adopt a user-centered convention. This means above all:
- avoid redundant complexities
- be explanatory and making the class syntax easy to read
- ensure order and cleanliness
- attempt to create a flexible model, ready to change and evolve whenever people require it
All these considerations led me to what I call UFOCSS, which essentially means bringing the user back to spotlight, by providing him with as much information as possibile, through a guiding CSS Architecture.
What does UFOCSS stands for?
Despite of what its name suggests, UFOCSS is the acronym of User Friendly Oriented CSS. This is not an alien-like methodology and it’s not really a new way of thinking about scalable and modular CSS architecture. It’s rather an attempt to focus on the most ‘human’ part of what’s already valuable out there. What does this entail? Let’s break it down!
I’m supposing here to be working on a large web project, where SCSS is used together with PostCSS in development environment. This way, we can categorize CSS and organize it into smallest logical units, by splitting our code across multiple folders and files that reference each other using @import
directive. The source files will be then used by the build system to compile them into a single stylesheet for production that will be output in the destination folder.
All told, the stylesheets directory might look like this:
stylesheets/
|
|– abstracts/
| |– _colors.scss
| |– _functions.scss
| |– _fonts.scss
| |– _grid.scss
| |– _mixins.scss
| |– _variables.scss
| |– _zindex.scss
| ...
|
|– modules/
| |– _b_{base}.scss # Base Layer
| |– _l_{layout}.scss # Layout Layer
| |– _o_{object}.scss # Object Layer
| |– _u_{utility}.scss # Utility Layer
| |– _v_{vendor}.scss # Vendor Layer
|
|– _mq.scss # @media rules
|– _tools.scss # third-party tools
|– _vendors.scss # third-party styles
|– main.scss # primary Sass file
As you can see, we’re splitting the code in just two main folders:
- abstracts
- modules
This helps us to keep the directory structure clean and tidy, without adding extra folders that are not really useful.
You can choose whatever proper name you like for the folders (i.e. tools for abstracts and patterns or layers for modules), I’m just applying here the convention of sorting the folders by alphabetical and numerical order. This convention turns out to be really useful when dealing with a language based on cascading and inheritance principles.
Arranging files and folders in alphabetical or numerical order gives the users a clear visual indication of what comes first in the code, which in turn should be ruled by the principle of specificity.
If you imagine your project as a layered sponge cake, It’s like saying that:
- you cannot build the top levels if you haven’t built the bottom first
- what affects more on the taste is the ingredients used on the top layers of the cake, when all the ingredients are used in the same quantity (Cascading)
- if you’ve used a lot of chocolate chips to fill in the bottom layer, no matter the layer anymore, the dominant flavor will be the chocolate! (Specificity)
The Abstracts folder: where the Tools live
The first information to provide is about what can be considered exclusively a tool, meaning that it does not generate any actual CSS rules, as opposed to what is instead the real style core of the project. For this reason, I think it’s important to keep the abstract tools apart from the rest, and this is actually a good widespread practice.
Back to the cake sample, the first step is to know exactly what you’re going to make and which design and taste your ‘cake’ will have.
The abstracts are basically the ingredients and the tools we need to get started and speed up development, such as variables, functions and mixins. They do not affect the look and feel of your ‘cake’, but exclusively the way you will build and maintain it. No one will really care about them, except you and your team!
All the abstracts files are pretty self-explanatory. Just consider that I’m taking everything that should be used extensively — like colors, fonts, grid and z-index — out of the _variables file. This way, I think it’s definitely easier for everyone to figure out where the tools we need are.
For example, the z-index abstract file can really help us to maintain the stacking order of the elements. A good practice to manage z-index for complex layouts is to set up multiple Sass lists which represent the order in which we want our elements to appear within a stacking contest, from lowest to highest.
Consider that you want a modal to create a new stacking context. We can simply create a new Sass list in the z-index file:
$modal-elements: fields, form-controls, alerts, autocomplete-dropdown;
As you can see, this Sass list is just a tool that helps us to safely manage the stacking order of the elements, but it’s not generating any CSS rules.
In order to use this tool and to retrieve the z-index value to assign to each element, you can use the index()
Sass function within the correct module file, as shown below:
.modal-alerts {
z-index: index($modal-elements, alerts);
....
}
This is just an example to show you how the abstract tools can really come in handy when working on big projects. Once you have defined your tools, you can finally dive into the real core of the project.
Let’s take a closer look at the Modules!
The Modules folder: where the Layers live
2. The principle of proceeding according to levels of increasing complexity and specificity enables us to add the second brick in the CSS wall, which is about the definition of CSS layers.
The abstracts layer we’ve seen before can be considered as a zero-layer. After having picked up all the needed tools and ‘ingredients’, we can start styling our project through progressive layers of abstraction, which are described below.
Identifying layers of abstractions can help us to systematically create modular stylesheets that will remain consistent, scalable and maintainable as the project grows and changes over time. This is why I group them under a folder called modules, as SMACSS does. This gives you the idea of a patterns collection, some sort of Lego pieces with different scopes that we can reuse over and over. The modules are basically some set of rules that can be applied over and over throughout the project as repeated and standardized units. They represent the real core of the project, since this is where we start to output actual CSS rules.
From now on, we’re going to use specific prefixes to name files, based on their CSS reference category.
The use of prefixes to easily recognize the class scope is a good practice adopted by the main CSS Architecture Systems, such as SMACSS and ITCSS. I will delve into the details of the naming convention in my next article, just consider now that I’m extending the prefix practice to the file names. This way, these goals can be achieved:
- Developers are encouraged to think more about the actual function that the style rule is supposed to have, so helping to identify its right place and order in the codebase.
- Everyone knows exactly what a file is used for.
- The prefixes used are alphabetically ordered. This way, they are shown in the same order they are imported, which is ruled by the principle of specificity (bases come first, then followed by layouts, objects, utilities and vendors layers).
The idea of separating the CSS codebase into several layers comes from ITCSS, whose main principle is to order the stylesheet from generic styles to explicit ones, from low-specificity selectors to more specific ones. I find this approach to be really useful when dealing with specificity, which is by far one of the most tricky principles of CSS.
What I’m doing here is just trying to simplify the directory structure, reducing and renaming the layers introduced by ITCSS: Settings, Tools, Generic, Elements, Objects, Components and Trumps.
- It makes sense to me to group variables, functions and mixins under the same layer which I call Abstracts, rather then splitting them between Settings and Tools.
- Generic and Elements can reasonably be merged into the Base layer, since they both include really basic and low-specificity classes.
- I prefer rename the Objects layer as Layout, which is by far more self explanatory, since it is used for no-cosmetic classes.
- Components is what I call Objects instead, since it can be used also for more atomic elements.
- Trumps is used for utilities, that’s why I simply call it Utility layer.
In case you need it, nothing limits you from adding an additional layer, such as the templates layer, that you can use for style rules that are uniquely applied within some templates. I would use the prefix _t_ in this case. Otherwise, you can just consider the following layers.
Bases Layer
This is the first layer which generates actual CSS rules. We place here reset or normalize styles, global rules such as box-sizing definition, and the style for bare typography HTML elements. Also, I think that we can reasonably place at this level some helper classes strictly related to HTML tags such as .h1-like, .small, .mark, that can really come in handy when there is no correspondence between style and semantics.
This is definitely the right place where you can place typography style rules like the ones below:
h1, .h1-like {
font: {
family: $font-primary;
weight: bold;
size: 2rem;
}
text-transform: uppercase;
......
}
I would include these rules in a file called _b_typography.scss
.
The prefix _b_ stands for base.
Layouts Layer
This is where we create the layout pillars of our project. This layer contains the main grid classes and other class-based selectors which define the skeleton of the website. We’re not still taking care of the make-up at this step.
Take the case of an article card including an image and some texts below it. When styling the makeup of this object, we shouldn’t care about its layout. It should work fine wherever it is placed! This means that we should delegate the responsibility of the layout to another class specifically created for this.
As a block element, the card object would naturally take up the full width available of its parent element. If we want the card to occupy just a portion of the available space, we should write another class which will be exclusively in charge of the object layout.
The idea of separating structure and design is borrowed from OOCSS (SMACSS and ITCSS adopt this principle as well), and I think it’s worth continuing to stick to it, since it helps the developers to easily figure out the scope of the classes and reuse them whenever needed.
The same object may have different layouts and the same layout may be applied to different objects, so it makes sense to separate layout and make-up classes to facilitate their reuse over and over.
Suppose that your customer wants to position up to 6 block elements per line. This has clearly nothing to do with the design of each element, so we can create a file named _l_columns.scss
which deals specifically with positioning specific block elements.
$min-cols: 2;
$max-cols: 6;.l_columns-1 {
display: grid;
grid-row-gap: 30px;
grid-column-gap: 30px;
}@for $i from $min-cols through $max-cols {
.l_columns-#{$i} {
@extend .l_columns-1;
grid-template-columns: repeat($i, 1fr);
}}
This way, the customer can simply edit the layout class of the parent container in order to modify the layout. Adding a class like l_columns-3
will automatically change the position of the block elements. You can play around with this by forking my pen: Dynamic layout classes using CSS grid.
As you can see, these layout classes have nothing to do with the make-up, which we will handle with under the Object layer.
The prefix _l_ stands for layout here.
Objects Layer
This is where the majority of our work takes place. We can gather here all the UI objects that can be considered as atoms, molecules and organisms (atomic design) or elements and blocks (BEM) from a conceptual standpoint.
When analyzing the project graphics, it’s fundamental to make the distinction between atomic elements and more complex components, but when it comes to code I find this distinction to be too verbose and not really useful, so I just prefer to group them under the same category. The name of the object can be a sufficient indication of its complexity level. For instance, we can easily presume that the buttons are atomic elements, whereas what we call cards is more likely a bigger component which can include buttons inside it.
Back to the previous example, it’s under the Object layer that we will define the makeup style to use for the block elements, such as thecard-preview
object:
.card-preview {
padding: 10px 25px;
border: 1px solid $border-primary;
text-align: center;
}
We can reasonably place this CSS rules in a file named _o_cards.scss
.
The prefix _o_ stands for object.
Utilities Layer
This is the where we place utility classes such as .text-center
, .font-small
, .visually-hidden
, bg-dark
. Utility classes are used to apply a single specific rule to an element, so we should guarantee that the style explained by the class name is actually applied. This requires us to write high specificity classes and for this reason this is the only place where the use of !important
should be allowed.
Since they bring styling right into the markup, they are definitely not the best choice when first styling objects. Instead, they can really come in handy for editorial purposes. This is where the single responsibility principle can be successfully applied, since this gives the editors the chance to customize a specific property of an element.
The prefix _u_ stands for utility.
Vendors Layer
this is where we override the vendor styles. As for the utility classes, they are generally high specificity classes. This is why we place them at the end of the stylesheet.
The prefix _v_ stands for vendor.
In my next article, I will talk more about how to handle with:
- Naming convention
- Media query
- State Rules
Meanwhile, any human and alien feedbacks are really appreciated!