CSS: The bad bits (and how to avoid them)

The more I use CSS, the more I think it’s an absolute wild wild west. It’s mature, ubiquitous, yet lacks basic, common-sense features that come as a given in other similar tools. After working with several modern MVC web apps that have well architected back-ends, extensive test coverage and good documentation, I often find the stylesheets to be giant steaming piles of spaghetti garbage.

I’m probably being unfair. It was born in a time when the web was still developing, ideas were being thrown about and popular ones were starting to stick.

As difficult as I believe CSS is, every website in the world needs it and couldn’t survive without it. So here’s some of my thoughts on what’s bad and how to keep sane when writing it. It’s going to be a working list, so anything else that bugs me in the future will get tacked onto the end.

1. Global scope #

My first and biggest qualm with CSS, is that any style declaration can change any aspect of any element on a page. This is super powerful and great if you’ve just started dipping your toes into web, but for bigger sites, more often than not, it’s very dangerous. Almost all front-end developers at one point in their careers will add/update/remove a style, only to find it accidently leaks into some other section of another page.

I think this is the most significant issue with CSS. It makes any concept of modularity very difficult. Tomorrow a developer could just stick in a few !important tags and scrap your nicely encapsulated, reusable widget.

.blog-post {
  .title {
    font-size: 2rem;
  }
}

# Grrrr, styles leaking everywhere
h1 {
  font-size: 5rem !important;
}

SASS/SCSS helps us here, but not completely. If you’re a good little dev, then leaky styles can be significantly reduced by nesting rules in a parent class.

.blog-post {
  .title {
    font-size: 2rem;
  }
}

# A bit better
.home {
  h1 {
    font-size: 5rem;
  }
}

This is better, but it isn’t a fool proof solution. It only takes one careless style to take you back to leak hell. One tight deadline which forces you to make some questionable compromises (i.e. hacks).

If you told any back-end developer that they had to use a programming language that gave all variables global scope, made every object’s internal state visible, let any other developer override their code, they’d probably resign on the spot. But that’s the insane reality of CSS development. Everything’s up for grabs. Immutability? Pfffshh.

Best workaround #

The best way around global scope that I’ve come across is to adopt the CSS naming system, BEM, which expects developers to consistently write their code in a modular way. It shuns dangerous leaky habits like styling using tags (e.g. h1, p, etc) and instead prefers styling on class names. Class names which are deliberately namespaced to the module (or Block in BEM terminology) that they live in, as to avoid accidental collisions with similar class names.

Often there’s an argument to use tags to create a default set of styles. For most sites, it’s usually a good idea. However, if you’re just overriding those styles pretty much everywhere, I’d say don’t bother. Put them in generic utility classes (e.g. .paragraph, .heading-1, etc) and use them as you need.

BEM is by no means perfect. It can be onerous writing big, long, namespaced classes everywhere. You’re still writing CSS. You can still write dodgy styles that screw everything up. You still need discipline. But what it does give is structure and a set of rules that will help you write better and more maintainable styles.

2. Specificity #

Specificity is the mechanism that CSS uses to decide which styles are more important than others. In my experience, it nearly always ends up in a race to the bottom (or the top in this context). A strew of !important tags, styles on arbitrary IDs and unneeded div wrappers whose whole purpose is to nudge the specificity just a bit higher than troublesome existing styles. True stories.

If you’re making a new UI element, but another very specific style written by your colleague is standing in your way, would you:

  1. Invest time understanding your colleague’s UI widget, figure out the impact of a change to the style to other parts of the site, refactor accordingly.
  2. Or bodge it with an !important.

Depending on the kind of environment you work in and the size of your codebase, your choice may vary.

Object-oriented languages get round this problem via inheritance and subclassing. The least “important” and unspecific methods are at the top of the inheritance tree, most specific at the bottom. In my opinion, application of these OO principles in the realm of UI work much better than CSS’s specificity. Anyone familiar with iOS’s UIKit for example will be familiar with making generic UIView widgets alongside more specific subclasses that override certain attributes. On the other hand, you’ve usually got to write a bit more code and spend a little more thought on this kind of override behaviour.

Best workaround #

Don’t participate in the race to the bottom. In general, make sure your styles aren’t too specific.

If you’re using BEM, you’re already following these rules. 👍

3. Implicit percentage rules #

Anyone who has used percentages in padding rules before will be aware of how confusing things can get.

.sidebar {
  padding: 10%;
}

If you’re new to CSS, the above style may give you pause for thought. It’s not clear whatsoever how the 10% is going to act. 10% of what? It could be of the…

Totally unclear on the face of it. In actual fact, it will calculate the percentage using the width of the parent element. This gives some unintuitive results, such as the vertical padding changing when the browser’s width changes instead of the height (incidentally, this weirdness is often used to hack responsive aspect ratios). It also means that if you wanted your vertical padding to respond to the height of your container, you just can’t. 😞

In an ideal world, whenever you use a percentage you should have to explicitly specify what value it should correspond to. I think that’s reasonable to expect.

Best workaround #

There’s not a lot you can do to get around this unfortunately.

If you’re looking to size things relative to the viewport width and viewport height, then I’d recommend using the vw and vh units. The support is pretty good and you don’t have to faff around with percentages. Just watch out for peculiarities on mobile as the viewport width and height can change depending on if the browser chrome is showing.

In terms of tackling the implicit percentage issue, I think the approach of adding units like vw and vh is a good solution. I just wish they took it a bit further.

5. z-index #

When z-index is causing headaches, a common solution, the nuclear one, is: z-index: 999999. Once this happens, chaos ensues. Anything that needs to appear above it in the future will likely include an extra 9.

I think it all starts to go down hill due to a lack of context. Because z-index rules are specified individually across all stylesheets, the relationships between them isn’t clear.

Best workaround #

If we’re disciplined, SASS/SCSS can help us out here. Defining all our z-indexes as variables together gives us the context we need.

$z-index-page: 100;
$z-index-navigation: 200;
$z-index-newsletter-modal: 300;

We can clearly see our newsletter modal goes above our navigation, which goes over our page content.

I also tend to work in hundreds so we can slot other z-indexes in between if we need. This doesn’t matter as much if every z-index is stored like above, since you can just easily update all the values just in the one place.

5. You have to use it #

Like JavaScript, CSS is the de facto language of the browser. It’s the only option for styling web pages, so has to be accepted, warts and all. Hopefully, some day browsers will become language agnostic. Someday.

On the other hand, CSS is used (fairly) consistently across multiple different platforms and isn’t the walled garden that iOS, Android and the rest of the tech eco-system is becoming.

Best workaround #

Quit front end web development. If that’s not an option, tough, you’ve got to use CSS.

‹ Previous
Promoting CSS reuse
Next ›
Message Oriented Programming