Writing stable, cross-browser CSS isn't nearly as easy as it should be. Wouldn't it be nice if you could easily avoid some of the most painful CSS pitfalls?
In this post, I'm going to follow the spirit of Martin Fowler's classic code smells and list a small number of CSS-specific smells that cause bugs and limit productivity. I'm limiting my list to writing for modern browsers and IE versions 7 and later. This isn't a definitive or final list, and I plan to add to it as I get feedback and advice.
As Martin Fowler points out, "The best smells are something that's easy to spot and most of time lead you to really interesting problems." Not every instance of a code smell indicates that there's a problem, but the presence of one should at least cause you to pause and think.
Nothing on this list is original. I think of it as a complement to CSSLint tool by Nicholas Zakas and Nicole Sullivan, which automatically scans your CSS for a set of problems. I'm also a huge fan of Nicole Sullivan's OOCSS methodology and I've liberally repeated some of its ideas in this post.
My (least) favorite code smells
Here's a small list of some of my least favorite code smells.
Smell: Width/height properties mixed with padding/margin/border properties
I stole this one straight from the CSSLint
box-model rule. An element's actual width and height is the sum of its content area width/height, its padding, its borders, and its margins.
To make things look right, I used to do mental arithmetic. If I had a 300px-wide module that needed 10px of horizonal padding, I'd define the width to be 280px and the horizontal padding to be 10px. This approach didn't work for three reasons.
- The arithmetic made things harder to understand at a glance that my element took up 300px of horizontal space and not 280px.
- I've found several cases (see screenshot below) where a container's padding varied depending on its content. It didn't always make sense to preordain a container's padding.
- It just won't work for containers with fluid (percentage) width and fixed (pixel) padding.
There are two workarounds. If you don't need to support IE7, you can use the
box-sizing: border-box CSS property setting. This will cause the width and height properties to set the width/height of the content area, padding, and borders together. An alternative workaround that works before IE7 is to add padding and borders on contained elements only. e.g.,
<section class="aContainerType"> <!-- width and height applied here. --> <div class="body"> <!-- Padding and borders applied here. --> </div>
This workaround feels a little icky, but it works consistently and it's remarkably flexible.
Smell: Overuse of explicit widths
If your layout catastrophically breaks when you change the width of a column or container, then you're probably overusing explicit widths.
Twitter's website gives a good example of how to do things right. The right column has a width of 522px.
But, if I hack the width to be 300px, the tweets still lay out in a reasonable way:
Make your container widths fluid except when they have to be fixed. You might need to practice a little to make this work, but, once you have the hang of it, you'll love the flexibility that fluid widths provide you.
Smell: Application semantics
Avoid CSS class names that say too much about your application's behavior and too little about its design language.
But shouldn't markup and styles be semantic? The meaning of "semantic" is a little confusing. CSS semantics are visual. For example, the classes
buttonSmall are decent class names because they describe two types of buttons that your design allows. The classes
hideProfile are poor choices because they likely refer to a visual style that applies equally well to other behaviors. e.g., a button or link style.
Why does this matter? If your application succeeds, its functionality will expand dramatically. But its CSS should remain relatively small in size. If you tie your classes to your design language instead of to your application's functionality, then you'll easily be able to apply them to new functionality while writing a minimum amount of new CSS.
To see a good example of well-defined class names, see Bootstrap's button color and list styles.
Smell: Deep nesting (aka high specificity)
Deeply nested styles – those preceeded by a long chain of IDs or selectors – are very hard to reuse. This is the core message of OOCSS. This smell is similar to the Application Semantics smell. A section header is a section header is a section header. There's no need for its styling to be nested under
If you have multiple styles of a particular type (say, multiple section headers), then define style variants that work across the board and separate content styles from container styles. Here's a simple example from my company's style guide:
Writing good container-independent styles with low nesting is tricky and it takes some practice. I highly recommend reading Nicole Sullivan's blog (I love this post) on the subject and watching this video.
Smell: Hand-rolled float layouts
This one will be the most controversial. Floats cause a ton of layout glitches because floated elements are taken out of the document flow. Floats were intended to support flow of text around images. But, because developers lacked alternatives, they used floats to lay out columns. There are subtleties involved in correctly floating elements and in clearing floats. And I've seen a lot of people trip up on those subtleties.
Either take the time to learn how floats work, or use a grid framework. I advise the latter because I've found it easier for people to grasp how grids work than to understand the nuances of floats.
The future is brighter
While I don't think we'll ever love CSS, it's going to get a lot cleaner in the coming years. The flexible box model should make more trickly layout problems easy to fix. And the eventual obsolecence of IE7 will make CSS authoring more fun still.
In the mean time, keep practicing! And please send along your candidates for CSS code smells.