I have written a lot of CSS, and I have honestly never been able to stomach BEM. It claims to make organizing, authoring, and reusing CSS simpler, and if you weigh it against spaghetti CSS, it probably does. But it comes at a cost — ugliness and repetition.
I spoke with a number of people who had used BEM while researching this article, and this was the one thing that everyone admitted was a major shortcoming. It’s ugly — super ugly. This is what kept me away from it in the first place, so I wasn’t surprised. But the consensus among those who I spoke with was that the ugliness was the price paid for an organization system with clear rules and guidelines which kept everyone on the same page, rather than what their CSS codebases were previously — a total mess. BEM users appreciate that it helps to avoid CSS scope leaks, and ensures a consistent style.
Fair enough. But we can do better. CSS architecture can be clean, pretty, avoid scope leaks, and impose an organization system. I know this because I have been quietly working on a CSS architecture methodology for years that does exactly this. And it works. So here I am to present it to you and save you from dealing with the horrendous ugliness and repetition of BEM, so you can live a better and more care-free life full of clean and beautiful CSS. I call this system GPS, which stands for global, page, section. We’ll get into the details of GPS soon. But first, let’s go over some of the points of tension it addresses.
By far the largest issue developers have with CSS are what I like to call scope leaks. These happen when you write styles for one specific section of a website, but because of the way you made the selection, the styles also affect elements on other random parts of the site. This is a side effect of CSS being written in the global scope by default. This is a really bad problem, as most of the time you don’t recognize it until someone else does, and it’s a production bug. One of the primary aims of BEM is to avoid scope leaks, and it solves this by eliminating nesting, and using long, obscure class names that are unlikely to be the same as any other class name by chance.
My system deals with this in a different manner — one that is clean, allows nesting, and doesn’t require an extra build process. We’ll get to that in a minute though.
Don’t Repeat Yourself
Any programmer knows that repeating yourself is one of the major sins in programming. The entire purpose of programming is to allow you to abstract out variables, functions, etc that are used more than once, so that you don’t need to find and replace when you need to make changes. It also opens up lots of opportunity for error when you change one instance of a construct without realizing another one exists and you forgot to change it.
This is also an important principle in CSS, as has been demonstrated by the meteoric rise of preprocessors which include variables, mixins, loops, etc.
Unfortunately, a side effect of BEM’s organization system is a large amount of repetition. For example, you might see this type of code with BEM (and this is a fairly clean example, it gets much worse):
Now, looking at this as a programmer, what’s wrong here? What’s repeated unnecessarily? Ding ding! Let’s see how this would look if we cleaned up the repetition:
Better. But wait, there is still some unnecessary repetition here. All of the list items can be selected with just .menu > li, but also have a class on them. Let’s make more cuts:
Nice. Now this is clean code. And within CSS, you can make a selection that’s just as accurate as the first BEM code example. In addition, our .menu list is just as portable, unless you plan on integrating one of your .menu__items into a different type of menu. But that would be a scope leak, and you’d have to change the name anyway.
This final code example is what would be recommended by GPS. And yes, it is possible without resorting to spaghetti code, and preventing scope leaks.
It is nearly universally agreed among developers that clean and readable code is much better than messy code. This applies just as much to HTML and CSS as any other programming language. Adding bunches of classes and ids with extra symbols in them to markup makes it more difficult to read and deal with. As we know, one of the major complaints of BEM users is its ugliness. It’s also extremely repetitive. This means that using BEM results in much less clean of markup that you’d have in an ideal world.
GPS strongly adheres to the philosophy that clean, readable, and semantic markup is of paramount importance to any website, and that a minimal amount of extra words and classes should be added to HTML in order to get the website styled correctly. And this can be done without all your CSS organization going straight to shit.
Ok, I’ve preached enough here, let’s get down to the details. GPS is a CSS organization system that breaks down your CSS into three distinct levels:
- Page (or View)
Let’s break each one of these down individually.
Some styles are used in multiple places across a website. For example, your h1 element might consistently look the same (and it should!), or you might have a .box class that wraps a piece of content in an elegant box, or a .button that creates a standard button on your site. These are good candidates for global styles. Global styles are great, they give a sense of unity to your site, and should be used frequently. If you’ve seen something like a brand manual or style guide, everything within represents global styles.
Since they are intended to be repeated, global styles should always be classes, never ids. In the same vein, styles should only be defined as global if and only if they appear on multiple different pages. Finally, to make it clear that a global style is being used, all global class names should be prefixed with g-, as in .g-box. If you are styling a raw element like ul or h1 with a global base style, however, it’s not necessary to add an extra class.
If you are working on a large website or app, it is extremely helpful to create a reference page for all global styles. This makes it easier for developers to start working on the project with a good understanding of what these styles are and how they are used.
Page / View
The basic criteria that define a page is as such:
- It is a major view, it would have it’s own page in a design mock
- It’s not possible that you would ever find more than one in the window at the same time
- It contains one or more “sections” (discussed below)
Page-specific styles are styles that do not apply globally, but do apply locally to all elements within a specific page. For example, you might have one page where there is a unique color to your header elements across the page, but no other page has this coloring.
Some people may have told you not to use ids, or that they are evil. This is ridiculous. Ids are intended to be used for elements that only appear once per page — it is invalid html to have more than one of the same id on a page. Therefore, their proposed purpose aligns precisely with GPS’ definition of a page/view.
This is the most specific level of selection. A section is defined as “a unique section within a page”. For example, on your about page, you might have a portion that contains an introductory paragraph, then a section that has some of your company’s staff, and a section that has some of the clients you’ve worked with. Each of these sections would be defined as it’s own section, and since there can only be one of them per page, these are also marked with ids.
If you are writing styling that applies only to one specific section, it should be nested under at least 2 levels of ids — the page id and the section id. This is the default place you should put code if you aren’t sure, as there is no chance of a scope leak for code here. Even if you have a section that is named the same thing coincidentally on another page, it will still not leak because it’s scoped under the page id as well. If later on you notice that the same style is actually used elsewhere on the same page, you can pull it up to a page-specific style. And if you notice it being used on other pages, you can pull it up to global. This is the safest route to take and prevents the most CSS errors. As soon as you find the need to use a block of CSS outside of a section, pull it up to a page-level style. And if you need it again outside the page, pull it up to global. If you are copy-pasting blocks of CSS in order to get around this problem you are doing it wrong.
This is the major secret to avoiding scope leak. The problem is that CSS is global by default. The solution is that you write your own CSS locally by default when you are using GPS. Start writing your styles under a page id and a section id. Then if you discover that it needs to be used over a wider scope you pull it up. This is much better than the opposite approach, where you write by default in the global scope, then narrow it down if you find that it needs to be more specific, because you are more in control of the scope.
Ok, now that the theory is out of the way, let’s take a look at how GPS looks in practice with a simple example. For this short example, we’ll consider an example site with 2 pages or views, home and about. Please note that this just an example, the methodology has been tested with, and works quite well on larger and more complex sites as well.
So here’s our home page:
<h1>Welcome to the Example Page!</h1>
<p>Aenean lacinia bibendum nulla sed consectetur. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum.</p>
<img class='g-full-width' src='example.jpg' />
<img class='g-half-width left' src=example2.jpg />
<img class='g-half-width right' src=example3.jpg />
And here’s the about page:
<img class='g-full-width' src="us.jpg" />
<p>Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Etiam porta sem malesuada magna mollis euismod.</p>
<img src="staff/john.jpg" />
<img src="staff/kate.jpg" />
<img src="staff/adam.jpg" />
Ok, so this is some fairly straightforward stuff here, nothing crazy. Note how each page has an id on body, and each section on the page has a div wrapping it with an id that describes what it contains. Also notice how this is very easy to read. Without even seeing any part of the styling, just by reading the HTML, you can imagine how this page might look.
Now let’s look at how the CSS structure might look. I won’t be including any actual styles here since this is an example of how to select rather than how to style your page. So the code examples will be pseudocode with just the selectors. The selectors will also be nested as SASS or any other preprocessor with nesting would do. I prefer splitting files into their page levels as well and concatenating them on build, so I’ll split them like this, although if you want you could write everything in a single file.
Notice how there’s no nesting here. The only time you should see nesting in the global CSS file is if you are handling some sort of hover or active properties, or if you have a global style that comprises multiple elements (which, to be fair, does definitely happen). These are all re-useable styles across the entire site.
Here we see a nice clean structure. You’ll notice that the h1 in the #banner section is a little different from the global style, so we have an override for this in the banner section. If it were the exact same, we wouldn’t need to select anything in the banner section. Another nice thing about having such cleanly separated scopes is the ability to be very granular in your override styles. This way, you can have a global style that is different in just one section without touching the global style at all.
Again, quite simple here. We have an override for the intro paragraph (perhaps a little larger text), and styles for the staff images that only apply to images there (perhaps they would have a border-radius and be floated so they align horizontally). The rest is covered by the global styles. Obviously this is a very simple example, but should be enough to get the idea across.
Another nice benefit you might have noticed is the use of ids. A lot of the time, people will tell you not to use ids, just because it’s possible to use them wrong where it’s not possible with classes. But ids serve a few needs here. First, they force you to think about re-use, which is hugely important. If you find yourself putting more than one id on a page (which is invalid HTML), you know you might have categorized incorrectly and should re-organize. The ids also act almost as headlines within your stylesheets. They sit at the base or second level consistently, and are used as major categories. It’s much quicker to jump to the section you need within a larger stylesheet by scanning the ids than looking for arbitrary selectors. I think of the ids in my CSS as I think of headlines in my writing, and conveniently, they even visually differentiate themselves as such.
There is no methodology that is without its shortcomings, so we must also discuss the other side of the coin — the dangers of using GPS. By far the most important ones have to do with global styles. Global styles, by nature, are extremely dangerous, as they have the capacity to affect many elements across a large website or app. Changes to global styles should be treated extremely carefully, and always audited by multiple developers and/or QA before being shipped. Any issue that we have run into with GPS at my company has been related to a change in a global style breaking something unseen.
As such, I’d recommend crafting your global styles (and style guide, optionally) as the first step in making your website. Make sure they are solid, reviewed, and adhere to the design guidelines — then move forward. That being said, no other CSS system does not also have this danger, so it is not unique to GPS, and it is generally a good practice no matter what to be very careful with any global objects in programming, and to carefully design them first.
Real World Use
I have been using this system for about 3 years now, both at an agency where it was deployed across a large variety of different short to medium term projects for a large variety of different companies, and at my current employer, HashiCorp, where it is employed across 10+ different brand websites that are all long term projects. There has never been an issue with onboarding new devs or scope leaks in either of these situations.
This might be a major departure from your existing CSS style, but I can almost guarantee you that it is much cleaner and easier to deal with, and provides the same benefits otherwise. If you follow the guidelines, it’s impossible to encounter a scope leak. It’s much less repetitive — in fact, it strongly encourages completely DRY CSS. And finally, it’s clean, easy to read, and takes advantage of the natural nesting abilities of CSS. On top of that, it is entirely re-useable. Since you have clearly delineated sections and global styles, you can pull any single chunk of CSS and reuse it without issue. It’s quick and easy to tell whether a given section implements global styles by looking for the g- prefix on the class.
Give it a try, and please leave any feedback or comments here. We have a larger, more detailed spec that this article is largely based on, as well as a lovely little community chatroom for those with questions, comments, and/or concerns.