Back in the day, I had all my CSS in one file. Then I discovered Sass, and started separating everything out into different files. Today, I have a tried-and-tested approach to structuring my CSS to ensure it’s tidy, powerful and easy to use. I call it GLOO, which stands for Globals, Layout, Objects and Overrides. Let me show you…
Why organise it at all?
When you compile your Sass into regular CSS, it tends to spit out a single file. So why bother separating it in the first place?
- It makes it easier to find things. If you have a clear structure, it’ll be easier to navigate around, easier to debug and easier to extend.
- It allows each file to be focused on just one thing. Especially in a large project, it’s easy to get overwhelmed, so being able to see just the styles relating to a particular ‘thing’ is useful.
- The order is important. That’s the “Cascading” part of CSS – styles at the top of your file are overridden or extended by styles below them. Organising your styles on specificity helps you avoid those nasty
!important
hacks.
Building on the shoulders of giants
GLOO didn’t happen overnight. Nor did it happen in isolation. I’ve taken inspiration from other projects, such as BEM and CUBE CSS, which are awesome. GLOO is an evolution, not a revolution, and probably wouldn’t have happened without other people doing their own awesome things. And if GLOO doesn’t feel right for you, check out those other projects and see if they fit the bill better.
Globals
The first layer of my CSS is what I refer to as “Globals”, which means they apply generally to everything, but in a non-specific way. It’s where the setup happens, importing third-party libraries, defining variables, mixins and functions, and setting some really basic styles on the body
. The principle here is that if you only had the Global styles applied to your page, probably no one would even notice they were there, but once everything else is in place it all depends on the globals being there. It’s like the foundation of a building.
Here are a few examples of what I might include:
- Resets and third-party libraries
- The global
font-family
- The
font-size
on mybody
element, which will define how big arem
is everywhere else - Colours to be used in the project
- Breakpoints to use in
@media
queries - Mixins and functions that will be used across the project
Layout
Now we’re on to the way the site or page is laid out. We’re still pretty generic at this point, dealing with the structure rather than anything stylistic – that comes later. We’re talking about logical sections, grids, sidebar positions. The important thing here is that pretty much none of these styles should be visible. We’re defining regions of our page that will be filled with other objects. Like with the Globals, if you stopped here you still wouldn’t be able to see anything. It’s like a wireframe or scaffolding, into which everything else will sit.
Again, here are a few examples:
- Sections, which might be full width or define a centre column depending on the window size
- I like to define a
.grid
class to give me a consistent grid and column setup - Similarly, a
.flex
might be useful container - Vertical rhythm between consecutive elements
Objects
Now we’re finally ready to start making it all look pretty! An Object is a “thing” that you can see on the page. It might be a card, or a button, or a heading, or a hero, or a navbar, or a modal, or… well, you get the idea. Each Object is defined in its own file, and is only really concerned with itself – it should be pretty portable. Importantly, any object could theoretically be put in any space defined by our Layout – the object shouldn’t care about where it is on the page, that’s for the Layout layer to worry about.
Like in other programming languages, there are a few characteristics to bear in mind with Objects. A class should describe an object, and there may be any number of objects using that class. There’s a sense of inheritance, where child objects are more specific than their parent.
We can also think about Objects as having private and public properties that we can interact with. We’ve probably all seen HTML markup where an element has a long list of classes, with no clear indication of priority or validation:
<div class="card card--primary card--outlined card--active is-visible card--has-image">...</div>
And with utility-based frameworks like Tailwind, that list of classes can get even longer! But if we apply what we know about Object Oriented Programming from other languages, we might see that some of these classes actually represent properties, some of which should be mutually exclusive (i.e. it wouldn’t make sense to allow a .card
to have both .card--dark
and .card--light
at the same time).
An approach I like to follow is therefore to use custom HTML attributes instead, so that those “public properties” can be set in a safe way. You can still use utility classes too, if they are set up to work in the same way across any element, but generally speaking each object is likely to have just one class. The example below shows how we might set the theme (and we’d be able to check the value is valid, and fall back to something else if not), and whether it should be outlined (in this case it’s boolean, so we just check whether the attribute exists). All other styles would effectively be private properties, since we’re not exposing them directly to our HTML.
<div class="card" data-theme="primary" data-outlined>...</div>
.card {
// Card styles go here
// Colour themes
@each $name, $color in variables.$colors {
&[data-theme='#{$name}'] {
@include mixins.theme($color);
}
}
// Outlined
&[data-outlined] {
border-width: 2px;
}
}
Overrides
Of course, no matter how carefully we plan our project, there will always be exceptions. The client might demand that the background of a particular page is different to normal. The spacing between certain combinations of elements might cause problems in one place but not everywhere else. We might need an option in one place only that doesn’t justify a complete refactor.
So once we’ve got our main styles set, we finish with our overrides. Remember the “Cascade” in CSS – these should be the most specific styles we have. This may be the only place in your entire code base where you use ids instead of classes. You might be targeting specific pages. Just keep an eye on the size of your overrides – if it’s getting unwieldy, it’s likely you need to refactor some elements to make them more customisable so they’re no longer exceptions.
Conclusion
With all that done, we’ve got all our CSS glued together with GLOO (pun intended). We’re making deliberate use of the Cascade, keeping our CSS and HTML tidy and expressive, validating our inputs, separating concerns, making our styles portable and reusable, and making everything easy to work with.
What do you think? Do any of these recommendations work for you? Do you use a different model?