7 tips for writing better CSS

CSS stylesheets are a fundamental part of the web, but they are also one of the most neglected parts of modern web applications. Traditional programming languages give you a ton of organizational features: namespaces, classes, scope, blocks, etc. CSS has none of these constructs.

I recently released the WordPress theme that runs this blog on the free public WordPress Theme Repository. The theme repository has very high quality-assurance standards and a team of theme reviewers, who run test suites on each newly submitted theme as well as updates to approved themes. While I was preparing my theme for public release, I picked up on a lot of good CSS tips that helped me improve the maintainability of my theme’s CSS. I hope they will help you too:

0. Use a CSS Preprocessor

Before you continue reading, the first thing you should do is make sure you’re using a CSS preprocessor like SASS. CSS preprocessors are tools that let you write your CSS in a special stylesheet language, and then translate your code into actual CSS before it goes into production. They add programming language features to CSS like variables, nested blocks, mixins (CSS functions), inheritance, and single-line comments.

I choose SASS over other options like LESS or SASS+Compass because:

  • SASS is highly stable (it’s built in Ruby) and contains zero bullshit.
  • SASS supports an indentation-based syntax similar to python and yaml. LESS does not.
  • SASS requires zero configuration, as it should.

It’s easy to set up a loop that re-compiles your SASS when you save changes to it, using the tools I wrote about in my continuous integration post.

1. Stop nesting. Use classes.

When you write CSS, always remember to separate structure and presentation. You should not be adding extraneous elements to your HTML for presentation purposes. This also means that you should disentangle your CSS rules from the structure of your HTML. This is not okay:

<body>
  <header>
    <hgroup>
      <h1>Welcome to my site!</h1>
    </hgroup>

...

// Don't do this!!
body > header
  display: fixed
  top: 0
  hgroup
    h1
      color: white
      font: 700 2.25em/1.5 $serif

As a rule of thumb, you should only nest blocks like this when you mean it. A long selector like body > header div h1 is hardly ever what you want. This CSS will break if you ever move your HTML elements around or add/remove a wrapper anywhere.

Use classes liberally. Your CSS should not look like just an outline of your HTML.

In the long run, classes go a long way towards maintainable CSS. You can always do a recursive search through CSS files to look for class names, but you will have a hard time locating a CSS block like the one above if all you have is the resulting 4-part CSS selector. The above example could be better written like with classes like this:

<body>
  <header class="site-header">
    <hgroup>
      <h1 class="site-name">...</h1>
    </hgroup>
...

.site-header
  display: fixed
  top: 0
.site-title
  color: white
  font: 700 2.25em/1.5 $serif

On that note, you should also stop leaving <div class="clear-fix"></div> around in your HTML. I’ll show you how to work around this with CSS Inheritance:

2. Selector inheritance is awesome

Inheritance is a very powerful, underrated feature in SASS. (LESS doesn’t support it in the way that SASS does.) Selector inheritance allows you to make classes that “extend” other classes, like parent and child classes do in OOP. It doesn’t do this by stupidly copying their CSS rules, but by adding extra selectors to the parent class’s CSS blocks. Let me show you an example:

.content
  margin: 1em 0 1.5em
  p
    color: white
    margin-bottom: 1.5em
.summary
  @extend .content
  margin-bottom: 2em

...

// Would compile to...
.content, .summary {
  margin: 1em 0 1.5em;
}
.content p, .summary p {
  color: white;
  margin-bottom: 1.5em;
}
.summary {
  margin-bottom: 2em;
}

Any rule and child-rule applied to the parent selector will also be applied to the child. Child classes can also have their own properties that override the parent’s. Here’s a more realistic example of what you can do with selector inheritance:

You’ve probably run into the clearing float CSS problem before. If you’re not familiar with it, take a look at this sample code:

<div class="container">
  <div class="left">
    ...
  </div>
  <div class="right">
    ...
  </div>
</div>

...
.container
  border: 1px solid black

.left
  float: left
.right
  float: right

The intended effect is that the container’s 1px black border surrounds both .left and .right, but it appears that only the top border is displayed. The problem arises because floated elements are taken out of flow and now, .container has no height. There are several solutions to this problem, but the most common one involves adding an extra element after both floated div’s and giving it clear: both. Instead of adding extraneous presentation elements to the HTML, you can use selector inheritance:

<div class="container">
  ...
</div>

...
.after-clear-fix
  // "&" refers to the current selector
  &:after
    clear: both
    content: '.'
    display: block
    height: 0
    visibility: hidden

.container
  @extend .after-clear-fix

If you apply @extend .after-clear-fix to several elements, it will compile to a single long CSS selector whose body contains the clearfix rules, thus reducing redundancy in the final stylesheet:

.after-clear-fix:after,
.container:after,
.content:after,
.search-area:after {
  ...
}

SASS also supports a custom syntax that prevents the original class from being printed. Another powerful SASS feature is the @import directive, which works just like CSS’s import directive, but actually brings in the content of the target stylesheet if it is available locally.

3. Organizing your sass/ directory

Most WordPress themes have just 1 stylesheet that’s located at the theme root. When using SASS, my theme stylesheet is usually generated with SASS, while the SASS source files are located in a separate directory. SASS supports the idea of CSS partials. Partials are SASS files whose names begin with an underscore. They are meant to be imported into other stylesheets, and not compiled directly by themselves (although you can do that if you want).

The @import directive is also partial-aware and will match @import "reset" with a file named _reset.sass.

For larger projects, you should split your CSS into files that reflect the semantic role of the parts of the page they style. Additionally, you will usually have a few extra SASS source files for a CSS reset, JavaScript plugins, color/font variables, and mixins. Here’s my theme’s sass directory as an example:

  • style.sass — The target of the SASS compiler. WordPress theme metadata is stored in CSS comments, so I put my metadata at the top of this file. This source file also imports the rest of your source files.
  • _reset.sass — I use Eric Meyer’s CSS reset. It’s available in SASS form on Google.
  • _variables.sass — It’s useful to store all of your colors and fonts in one place and refer to them by variable names. In a monochrome theme like this one, I used color variables named $blue1 to $blue15 in order of increasing lightness.
  • _cobalt-global.sass — Buttons, input elements, and WordPress core classes
  • _cobalt-grid.sass — Grid for desktop and mobile layout
  • _cobalt-header.sass
  • _cobalt-primary.sass — The primary content area
  • _cobalt-secondary.sass  — Secondary content like sidebars
  • _cobalt-footer.sass

You can see that this naming scheme reflects traditional source code a lot more than CSS usually does, and because of the import directive, you don’t sacrifice any performance by splitting your CSS into files like this. One mistake that beginner CSS programmers sometimes make is separating the rules for a single selector into different files. They organize their stylesheets by the semantics of the CSS properties, rather than the semantics of their selectors.

// Don't do this!!
//_colors.sass
.header
  color: black
.content
  color: #CCCCCC
...

//_typography.sass
.header
  font: 700 2.25em/1.5 $serif
.content
  font: 400 1em/1.68 $sans-serif

When you split up your stylesheets like this, you end up having to edit 5 or 6 files even if you’re just working on one part of the site design. Usually there’s also a _mobile.sass thrown into the mix, which is also a bad idea. On the other hand, you don’t want to be writing @media only screen and (max-width: 767px) more than once. You can avoid both of these problems by using SASS mixins:

4. Essential mixins for any project

One of the reasons I don’t like SASS+Compass is that there is too much hand-holding and mixins that you’d never use. Most useful mixins are ones that you can program yourself in a few lines. Here are the essential ones that you might actually find useful:

Mobile Layout Mixin

$mobile1: 767px
$mobile2: 479px

= mobile1
  @media only screen and (max-width: $mobile1)
    @content

= mobile2
  @media only screen and (max-width: $mobile2)
    @content

This is the solution to the mobile layout problem I mentioned in the previous section. Mixins in SASS, as you can see, are defined with the equal sign. The SASS content directive is similar to the Ruby yield directive. Instead of typing out the mobile media query every time you need to use it (or even a lengthy variable interpolating selector), you can use the content directive in SASS 3.2+ to define your responsive mobile styles. Here’s an example of it in action:

.site-navigation
  float: right
  +mobile1
    float: none
  li
    display: inline-block
    +mobile2
      display: block

You should never make a separate SASS source file just for mobile CSS. Instead, mobile styles should be kept with the original non-mobile CSS so that you can see both the original and the overriding properties in a single place.

Vendor Prefix Mixin

= vendor-prefix($name, $argument)
  #{$name}: $argument
  -webkit-#{$name}: $argument
  -moz-#{$name}: $argument
  -ms-#{$name}: $argument
  -o-#{$name}: $argument

A vendor prefix mixin is always useful when when using CSS3 properties like border radius and transitions. However, remember not to depend on CSS3 properties for critical parts of the page. Occasionally, you will need to specify the argument on a separate line if it contains commas:

.button
  $transition: background 0.3s, color 0.1s
  +vendor-prefix(transition, $transition)

That’s it. These two are really the only mixins that you may ever need, and you could have written them yourself in a minute. As a rule of thumb, you should only turn a rule into a mixin if you’re using it at least 3 times, and if a mixin only has 1 property, you might as well use a variable.

5. Prefixes for your classes

Anyone who has worked on a large project has come across an HTML element with a class whose purpose was completely unknown. You want to remove it, but you’re hesitant because it may have some purpose that you’re not aware of. As this happens again and again, over time, your HTML become filled with classes that serve no purpose just because team members are afraid to delete them. The problem is that classes are generally given too many responsibilities in front-end web development. They style HTML elements, they act as JavaScript hooks, they’re added to the HTML for feature detections, they’re used in automated tests, etc.

— engineering.appfolio.com

This quote is from an article about CSS architecture by Philip Walton. He recommends that you prefix classes used by JavaScript hooks with .js- and so on, so that you can change CSS class names without breaking anything. I think it’s a great idea, and I’ve been using these other class prefixes as well: (most of these are for WordPress)

  • .site- — For classes that apply site-wide like site headers and footers.
  • .entry- — For classes that apply to a single element, of which there are many.
  • .nav- — For classes on navigation elements that apply to many entry elements.
  • .var- — For variants of classes (e.g. var-blue or var-small)

Intent-based class prefixes like these are advantageous over colloquial class names like .next-button or position-based class prefixes .bottom-navigation.  They let you form a natural class hierarchy using dashes so you can be sure that class names you create are unique. Above all, you don’t want to use generic class names like .container or .header without prefixes. They don’t convey much information about their intent, and they are easy to reuse accidentally, especially if you plan on using messy nested selectors.

6. Know your CSS shorthands

CSS is a vertical language. Lines are rarely more than 75 characters long, but blocks can span 20 or 30 lines easily. To conserve space on your screen (and the screens of those who are reviewing/maintaining your code), use CSS shorthands liberally. Here are a few essential shorthands forms that you should know by heart:

Box Model Shorthand

Numeric box-model properties like margin, padding, and border offer a shorthand notation. The order is clockwise from top, as demonstrated below:

.button
  padding-top: 2px
  padding-right: 3px
  padding-bottom: 4px
  padding-left: 5px

// Equivalent to:
.button
  padding: 2px 3px 4px 5px

You can also specify just 2 or 3 values instead of all 4. In this case, the missing sides will have the same value as their opposite side:

.button
  margin: 4px 3px
  border-width: 1px 2px 0

  // Equvalent to:
  margin-top: 4px
  margin-bottom: 4px
  margin-right: 3px
  margin-left: 3px

  border-width-top: 1px
  border-width-right: 2px
  border-width-left: 2px
  border-width-bottom: 0

Border Shorthand

Borders have 3 properties that are easily distinguishable. They also offer a shorthand form to write all three in one property. Note that you can’t combine the box-model shorthand with this one:

.button
  border-width: 1px
  border-style: solid
  border-color: #00477D

// Equivalent to:
.button
  border: 1px solid #00477D

Font Shorthand

The font property shorthand is the least-commonly used of these shorthands. It’s a hard one to remember, but it will knock out 3 or 4 lines of CSS every time you use it:

$serif: Georgia, Times, serif

.site-header
  font: 2.25em $serif
  font: 2.25em/1.6 $serif
  font: bold 2.25em/1.6 $serif
  font: 700 italic 2.25em/1.6 $serif

  // Final form is equivalent to:
  font-weight: 700
  font-style: italic
  font-size: 2.25em
  line-height: 1.6
  font-family: $serif

You can omit properties from the font shorthand as shown above. However, it must have at least the font size and the font family declarations in order to work at all. If you’re not familiar with numeric font weights, 700 means bold.

Background

The background shorthand is also rarely used because it is hard to remember. Backgrounds have just 4 properties: color, image, repeat, and position. Specify them in that order:

body
  background: blue url(...) repeat-x center center

  // Equivalent to:
  background-color: blue
  background-image: url(...)
  background-repeat: repeat-x
  background-position: center center

That being said, you should not use these shorthands if you only want to specify the top margin or the font size. CSS shorthands are most effective when most of their properties are utilized.

7. Alphabetize for easy comprehension

People have all sorts of different methods they use to order CSS properties. I tend to favor alphabetical order by property name, even if related properties like top and left are separated. You can take a look and pick which one you like yourself:

// Alphabetical order
.search-button
  @extend .button
  background-color: $blue
  border: none
  color: $white
  cursor: pointer
  font: 300 1em/1.25 $sans-serif
  left: 1em
  padding: 4px 9px
  position: relative
  text-align: center
  text-decoration: none
  top: 0.25em
  +vendor-prefix(border-radius, 0.25em)
  vertical-align: middle

Others prefer to order properties by theme:

// Thematic order
.search-button
  @extend .button

  // Box model
  border: none
  padding: 4px 9px
  +vendor-prefix(border-radius, 0.25em)

  // Positioning
  position: relative
  left: 1em
  top: 0.25em

  // Internal styles
  color: $white
  cursor: pointer
  font: 300 1em/1.25 $sans-serif
  text-align: center
  text-decoration: none
  vertical-align: middle

However you decide to order your CSS properties, the effect is the same. All of the tips listed here are purely for the programmer’s convenience. Presentation can always be achieved without CSS preprocessors or shorthands, but using these tools will make your CSS much more readable and maintainable.