Inspired by Mark Otto's tour of GitHub's CSS and Ian Feather's tour of Lonely Planet's CSS, I thought I would join the party and talk about how we do CSS at CodePen.

Overview

  • [1] We use SCSS.
  • [2] We use Autoprefixer.
  • [3] We use the Rails Asset Pipeline.
  • [4] The SCSS file that represents the CSS file that actually gets loaded is just a table of contents.
  • [5] We have a style, but mostly just because consistency looks nice.
  • [6] We don't use any particular architecture other than "use classes a bunch."
  • [7] We keep it to 2 or 3 CSS files per page.
  • [8] We use a @mixin for media queries which we can turn off.
  • [9] Commenting is a good idea.
  • [10] Some statistics.
  • [11] I just used the word "we" there a bunch, but it's mostly just "me".
  • [12] What the future might be like for us.

Preprocessing

There is always people loving-on and hating-on preprocessors, but I think it would be a joke to try and maintain any website beyond the very simplest types without a preprocessor. If you think they make me less productive because I'm over-tooling I LAUGH IN YOUR GENERAL DIRECTION.

I prefer SCSS because I like the community around it, but I also think it's better. Honestly though, all the major preprocessors (Sass, LESS, Stylus) do the big important stuff I would need to do.

Here's the features that I find useful in order of usefulness:

  1. @import
  2. @mixin
  3. nesting
  4. variables
  5. @extend
  6. math
  7. loops
  8. working with them enough so I understand all the cool kid demos

It would take some pretty incredible feature (that I can't even imagine) to switch.

Prefixing

I barely even think about prefixing CSS properties and values, because Autoprefixer does such a good job. I particularly like how it handles flexbox fallbacks.

I used to use Compass quite a bit, but found that 95% of what I used it for was the CSS3 @mixins. I prefer the approach of "just writing it like it should be" rather than seeing @include everywhere just for prefixing.

The one thing I miss from Compass was it's ability to generate SVG gradients. But... they were kinda weighty for something only IE 9 needed so I guess no huge loss.

Rails

I'm a fan of the Rails Asset Pipeline. For instance, I put this in a view:

  <%= stylesheet_link_tag "about/about" %>

And it spits out a CSS file where I need it.

  <link href="http://assets.codepen.io/assets/about/about-a05e4bd3a5ca76c3fb17f2353fedbd99.css" media="screen" rel="stylesheet" type="text/css" />

We set far-out expires headers on all assets.

expires headers

Every time we deploy, it breaks the cache by changing those gibberish numbers. Thus, nicely cached assets.

We do use Sprockets on CodePen, but only for JavaScript. That allows us to do like:

  //= require common/whatever.js
//= require_tree ./../hearting/

You can do that in CSS too, but why bother when:

  1. Sass can do it
  2. If you let Sass do it, the dependencies work better. Like $variables and @mixins are usable across files.

We also don't commit any CSS files to the repo at all, which is nice for Git. All assets get compiled on deployment.

Organization of Files

The CSS files that are loaded have an SCSS equivalent that likely doesn't have any actual CSS in it at all. They are a table of contents for what is in that file.

For example this is what our global.scss is like right now:

  // General Dependencies
@import "global/colors";
@import "global/bits";

// Base
@import "global/reset";
@import "global/layout";

// Areas
@import "global/header";
@import "global/footer";

// Patterns
@import "global/typography";
@import "global/forms";
@import "global/toolbox";
@import "global/buttons";
@import "global/modals";
@import "global/messages";
@import "global/badges";

// Third-Party Components
// (none at the moment)

I try and stick to this, but there are plenty of exceptions where I dump stuff into a file that should be all-imports. I probably should be creating a shame file and doing

  @import "shame";  // get organized, ya schlub.

Organization of Code

Style-guide-ish things I'm OCD about

  • 2 space indentation for properties and nesting
  • 1 space after a selector and before {
  • 1 declaration per line (really necessary to make difs useful at all)
  • 1 space after the :
  • Closing } on it's own line at same indentation level as selector
  • 0 as a length should have no unit.
  • Hyphens, not underscores

Style-guide-ish things I mostly do

Very related blocks have no blank lines between them:

  .thing {

}
.related-thing {

}

A little bit related things have one blank line between them:

  .thing {

}

.another-thing {

}

Things that are pretty unrelated have 2 blank lines between them.

  .thing {

}


.totally-different-thing {

}

Style-guide-ish things I don't care about

  • Order of properties. Related properties (e.g. height/width) are typically somewhat grouped, but meh.
  • What style of comments I use (they both can be useful)

In real life I'm not even as strict as my own guide.

Architecture™

My system is: give pretty much everything a class. I don't know if that's closer to SMACSS, OOCSS, BEM, or what, or if that matters.

That doesn't mean I don't nest things ever (i.e. creating selector combinators) or that I have a hard rule about how far I'm allowed to nest, but it does generally mean that nesting doesn't go very deep.

I regularly do stuff like:

  .box { 
  h2 {
    &:after {
    }
  }
}

And I wonder... should I have given that h2 a class? Should I be establishing that type of header as a reusable component? Then I stop caring, because I realize that if it becomes common enough I can easily do that later (and generally do).


The overall philosophy is keep specificity low. There will always be times you need to override it, no matter how cool-dude-reusable you're being, so the lower the specificity is on a selector, the easier it is to override. Not only that, but override in such a way you might even be able to override it again without going nutzo with an ID selector or !important.


rem is my unit of choice for font-size, px most other places. Plenty of exceptions.

Requests

I try and keep each page to requesting 2 or 3 CSS files:

  • global.css
  • page.css (if not the editor)
  • section.css (if needed)

The idea is to keep the number of requests low. But not take it so far as to squish absolutely everything into one file. I feel like there is so much page-specific CSS on CodePen that global.css would just be too damn big if it was all squished together. I've never actually tried it though. It would interesting to try someday. I'll probably call the branch squiCSSh_it_real_good.

Media Queries

I use a @mixin for media queries. Sometimes I like to think in "this width and bigger" and sometimes "this width and smaller" (media query logic!). It's wordy:

  @mixin breakpoint($point) {
  @if ($MQs == true) {
    @if $point == baby-bear {
      @media (max-width: 500px) { @content; }
    }
    @if $point == mama-bear {
      @media (max-width: 700px) { @content; }
    }
    @if $point == papa-bear {
      @media (max-width: 800px) { @content; }
    }
    @if $point == super-bear {
      @media (max-width: 1280px) { @content; }
    }

    @if $point == reverso-baby-bear {
      @media (min-width: 501px) { @content; }
    }
    @if $point == reverso-mama-bear {
      @media (min-width: 701px) { @content; }
    }
    @if $point == reverso-papa-bear {
      @media (min-width: 801px) { @content; }
    }
    @if $point == reverso-super-bear {
      @media (min-width: 1281px) { @content; }
    }

    @if $point == exclusive-baby-bear {
      @media (max-width: 500px) { @content; }
    }
    @if $point == exclusive-mama-bear {
      @media (min-width: 501px) and (max-width: 800px) { @content; }
    }
    @if $point == exclusive-papa-bear {
      @media (min-width: 801px) and (max-width: 1280px) { @content; }
    }

    @if $point == iOS {
      @media (min-device-width: 768px) and (max-device-width: 1024px), (max-device-width: 480px) { 
        @content; 
      }
    }
  }
}

I just like this naming convention. I don't have to reference this hardly ever, I just know what to use when I need it.

Notice the @if ($MQs == true) { at the top of the mixin. I set that variable to true or false at the top of all table-of-contents .scss files. That's because some pages of CodePen are responsive and some are not, but they will often use some of the same components. If the page isn't designed to be responsive (either it just has to be desktop, e.g. we've designed a drag and drop mechanic that doesn't work with touch yet) or the page has a mobile-specific version that we redirect to, might as well not include media queries in that CSS since they won't be used anyway.

Commenting

I'm pretty liberal with commenting. Mostly because I never regret it. I only delete them if I read the comment later and I don't understand it or it's clearly not relevant anymore.

  .drag-from-pen-grid {
  padding-bottom: 52px; /* adding this to make room for pagination. A little magic-numbery... */
}

Statistics

  • There are 160 individual .scss files. find /stylesheets \! -name "*.scss" | wc -l I never have issues with finding the file I need to edit because 1) find in project in Sublime is way fast 2) they are organized and named well, so command-t jumping to the files is easy.
  • There are 13,345 total lines of SCSS in those files. find stylesheets/ -name '*.scss' | xargs wc -l
  • global.css is weighing at 11.8k
  • page.css (used everywhere except the editor) is 5.5k
  • editor.css is 6.2k

CSS is not a huge performance factor for us. We serve one custom font that is 4× that and our JavaScript is 10× that.

Just Me

The three of us all have parts of that app that we all share pretty equally, and some parts that we individually own almost entirely. CSS is one of mine.

I feel compelled to mention that, because while I think I have a pretty smart system going, it might be totally insane and only work for my brain. One thing I need to get better at is putting TODO's in our issue tracker rather than in code. I don't have any system for revisiting TODO's in code, so they are often lost.

I hope someday we get to grow our team and I look forward to watching the system morph to fit more brains.

The Future

  • I'm not linting right now, but would like to. We are linting JavaScript and it's been good to us.
  • I'm not creating sourcemaps locally yet, mostly because I don't think it's working in Sass / Chrome at the moment?
  • I don't have a true pattern library built, public or private. The site is built from patterns though, so I think it would be good to build a visual pattern library, if nothing else to revisit a wide swath of code and identify areas that could be better or more pattern-y.