We recently launched the new Oozou website. Having worked on some projects (both mine and others; both inside and outside Oozou) with CSS code that’s not quite easy to maintain, this time I want to find ways to write CSS in a more maintainable and predictable way.
The Maintainability Problem
CSS is very flexible. It doesn’t dictate how you should structure your CSS rules, but putting CSS rules at different places can make your stylesheet less predictable. Maintainability problems could arise when CSS selectors cascade. This also applies to other CSS preprocessors, such as LESS and Sass.
In some projects, the Sass code follows the DOM structure. For example, for this HTML structure:
<div class="navbar">
<ul>
<li><a href="#">...</a></li>
</ul>
</div>
Comes this SCSS structure:
div.navbar {
> ul {
> li {
> a {
// ...
}
}
}
}
This is what I call the “selectorception.” The Sass Way calls it the
CSS Selector Nightmare. It is having the CSS structure tied closely to the DOM.
Not only is the resulting file size larger, it virtually makes component non-reusable. If something in the markup gets moved, the CSS rules has to be changed as well.
Using ID selectors a lot could also pose a maintainability problem. We may be tempted to write a stylesheet with rules that are too specific. Since there can be only one element using the same ID, they are not reusable.
Other things I see include CSS files that keep getting longer and longer. Some of them have more than 1000 lines.
Some Guidelines are Too Complex
I’ve read some articles and web sites about making CSS code maintainable, and they come with a lot of terminology such as the Base, Block, Element, Layout, Modifier, Module, Partial, State, Theme, and so on. They don’t have to be this complex, I think.
So while I developed the website, I also try to write a guideline for structuring CSS in the project, based on the idea of components. It is partly inspired by
Medium’s CSS guidelines, which are my favorite.
Thinking in Components
In the new website, I try to embrace the idea of components. We can think of a web page as being composed of multiple components. For example, let’s take a look at our “Expertise” section.
This section is composed of three components:
-
expertise is the entire expertise section.
-
container is a generic container that ensures some padding and limits the size.
-
expertise-item is a single expertise item.
Styling vs Layouting
To make it easy to reuse a component elsewhere, and to make the CSS layout manageable, we need to differentiate between styling and layouting.
Styling means changing colors and fonts, borders and paddings. All these happen inside the boundary of the component, and do not interfere with the layout of other components (except for element’s width and height). For example, these are all considered styling:
- background
- border
- color
- font
- padding
- text-align
Layouting means moving elements around and sizing them. They may affect nearby elements on the page. For example, these are considered layouting:
- clear
- float
- margin
- position
- transform
- top, right, bottom, left
- width, height
Don’t Layout Yourself
And here’s the rule:
A component should style itself, but give up the task of layouting to its parent.
This means that a component should not have margins on its own, or float on its own, but the parent component should layout its children.
Let’s look again at the Expertise section and its corresponding markup:
<section class="expertise">
<div class="container">
<h2>Expertise</h2>
<div class="expertise--contents">
<div class="expertise--item-container">
<div class="expertise-item expertise-item-m-icon-uxui">
<h3>UI & UX Design</h3>
<p>Our designers can perfect the user<br>
experience and make it look great.</p>
</div>
</div>
<!-- Repeated 6 times -->
</div>
</div>
</section>
For example, I wouldn’t write:
.expertise-item {
margin-top: 80px;
}
Instead, I would let the .expertise section do it:
.expertise {
.expertise-item {
margin-top: 80px;
}
}
This rule makes it easier to create a responsive UI, because it makes layouts more predictable. We don’t have to worry if two components will look good together on a screen size; just make sure that the parent layouts its children appropriately:
.expertise {
&--item-container {
@include media(720px) {
float: left;
width: 50%;
}
@include media(960px) {
width: 33.33%;
}
}
}
And each expertise-item takes care of the spacing between the heading and the text (and also its own styling).
.expertise-item {
h3 {
font-weight: normal;
font-size: $font-size-medium;
padding-bottom: 60px;
margin-bottom: 30px;
background: bottom center no-repeat;
}
p {
font-family: $sans-serif;
font-size: $font-size-small;
}
}
Keeping Components Simple
Componentization prevents selectorception. If the component starts to contain too many lines of code, I will try to factor some part into a separate component.
However, if I factor out just everything, I will run into the problem of too many components, which is also a maintenance burden. That’s why we also have sub-components.
In this project, we name the sub-component after the parent component, using two dashes in between:
<footer class="footer">
<div class="container">
<div class="footer--brand">
<a href="/">Oozou</a>
</div>
<div class="footer--copyright">
© 2014 Oozou
</div>
</div>
</footer>
This makes it clear that the “brand” and the “copyright” is a sub-component of the “footer”. Naming it this way also ensures that it won’t clash with top-level components — something that happens a lot in my experience.
With the help of Sass, we don’t have to repeat the component name in our stylesheets:
.footer {
&--brand a {
// ...
}
&--copyright {
// ...
}
}
Nesting subcomponents is not allowed. If a subcomponent becomes so complex that it needs to have its own subcomponents, then perhaps it should become a top-level component!
These rules force us to think again and keep components simple.
Component Modifiers
Sometimes we need to add some modifiers to a component. In our project, we do this by adding the name of the modifier directly to the HTML markup.
Here are our button styles:
To get an outlined thin button, we use this markup:
<a class="button button-m-outline button-m-thin">Button</a>
Component Inheritance
Similarly to object-oriented design, there are times when some component is a specialization of another component. Adding bunches of modifier classes to the HTML markup would make the markup very redundant, and not friendly to refactoring.
With the help of Sass, we could use @extend to extend other components. For example, our “expertise” component is actually a specialization of an inverted section. The CSS looks like this:
.expertise {
@extend .section;
@extend .section-m-inverted;
// ...
}
I won’t get into the debate about using @mixin or @extend. Here, we use @extend almost strictly for component inheritance, which I think is a valid use case. I think it is OK to use either, as long as they are done in a controlled manner.
Some downsides about @extend are that it won’t work across generated CSS files, and that you cannot extend from inside a @media query. But if you have to do that, maybe you have to think again about the structure of your CSS.
File Structure
CSS files with too many lines are not pleasant to work with. In this project that has many highly specialized components, our rule is: 1 top-level component per file.
Some projects may be so simple that you can put all your CSS in a single file. For some, it may be more appropriate to organize by controller (Rails’ default).
Just be sure to refactor as soon as it gets painful.
Directory Structure
Having a stylesheets directory with 100 files is also not pleasant to work with, so there has to be some grouping. I don’t believe there’s a directory structure that is best suited for every project.
In the case of our website, each page is highly unique. Many components are only used on a single page. Therefore, it makes sense for us to separate our CSS files by page. And hence, our directory structure looks like this:
- base
- Contains base stylesheet such as resets and scaffolding (we’re using Bitters).
- common
- Contains components used in multiple pages.
- index
- Contains CSS for the home page.
- work
- Contains CSS for the work page.
- …
Again, you should also find the directory structure that fits best with your project. For some, it’s simple enough that you could put all your CSS in a single directory (Rails’ defaults). For some, you may organize them by type. And for some, you may organize them by screen size or media type.
From Wireframe to Design, and From Design, Back to Wireframe
Given a visual design from our designer, it’s sometimes useful to go back and draw a wireframe. Why is that?
-
At a Glance: With a small wireframe, you can look at the whole page at a glance, so that you are not too focused on little pieces of the page.
-
Better Naming and Componentization: Having the whole page in a single glance lets you name each section more easily.
-
Structure and Hierarchy: It is also easier to structure the page into sections and subsections when you have a wireframe.
-
Margins Having a wireframe also allows you to plan how to use margins and paddings more efficiently.
On Responsive Design
Starting from largest (or smallest) screen size, resize the window slowly. Whenever a certain component starts to look weird or uncomfortable, think of how you can make a component look slightly better, and create a breakpoint there. Repeat until you reach the smallest (or largest) screen size you want to support (in our case, 320px).
Selector Usage
The way to write selectors should also be specified. In our site, we use something like this:
- .component-name
- .component-name–subcomponent-name
- .component-name-m-modifier-name
- .u-clearfix (utility)
- .js-google-maps (for JavaScript)
There are Always Exceptions, and This One is No Exception
As with all style guides, they should not be followed blindly. There are many times I break those rules on a case-by-case basis, and sometimes I change the rules too keep it suitable for the project.
Conclusion
As an intern student, working at Oozou gave me an opportunity to apply my web development skills on real-world and large-scale projects. I found that it takes much more planning and refactoring, compared to creating small side projects, to make a project pleasant to work with in a sustainable way.
Working on the company’s new website taught me how to write more maintainable and predictable stylesheets, by:
- Thinking in components.
- Separating styling from layouting.
- Keeping things small and simple.
- Organizing files and directories in a way best suited to project.
- Reorganizing and refactor often.