CodePen

CodePen's CSS

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.

Comments

  1. Dude @chriscoyier, thanks for sharing this. Always fun seeing how others structure their code. Love the variable names for the media queries.

  2. I love these CSS posts that are popping up. For the record, Sass sourcemaps totally work in Chrome right now! We've been using them on our projects, and makes our workflow so nice. We're generating them with Gulp right now, which isn't perfect, but with Grunt or straight-up Ruby it should work perfectly!

  3. Hey Chris, great post! It is very insightful to have a look at your CSS code base.

    Regarding your MQ mixin, I took the liberty to rewrite it to make it a bit more elegant: http://sassmeister.com/gist/1a0a43f1511b9bd75adf. Feel free to use it obviously. :)

  4. Hey Chris, great article. Its always nice to peek behind the curtain and see what others are doing.

    Regarding your forgotten TODOs: there is a useful Sublime plugin that may be of use. Try SublimeTodoReview: https://github.com/jonathandelgado/SublimeTodoReview

  5. Cool article, always nice to compare and contrast the work flow of other professionals :) particularly enjoyed laughing in the direction of :p

  6. Nice! The funny thing is, I have a very similar way of doing things (maybe because I've learned tons with CSS-tricks). But as I'm preparing the ground for more front-end devs where I work, I'm feeling the need of get things more "general". As in, make more people understand the code easily.

    Really cool article. There will be a CSS-tricks CSS?

  7. Thanks for taking the time to post this Chris, very interesting to see how you guys do it!

  8. I have used your site, information and guidance quite a lot so far in my journey and really appreciate everything you put out.

    All the best!

  9. A fantastic read...thank you for sharing...I will second Elton Mesquita's comment and ask if there will be a CSS-Tricks CSS?

  10. Have to admit that it feels good to see now that some of the things I do, or don't do for that matter, have the same justifications that Chris has. Like dropping Compass and use Autoprefixer instead.

    Awesome post.

    Thanks for sharing.

  11. Thanks for sharing your way of doing things!

    <3 codepen ;)

    Interesting to see these css/js/template workflows popping out from several different places. Task managers are proving to be a great and important tool nowadays. I use something fairly similar as well, using GULP.

    Cheers!

  12. You might be happy to know that you can @import multiple documents at once!

      @import 
    
    // General Dependencies
    "global/colors",
    "global/bits",
    
    // Base
    "global/reset",
    "global/layout",
    
    // Areas
    "global/header",
    "global/footer",
    
    // Patterns
    "global/typography",
    "global/forms",
    "global/toolbox",
    "global/buttons",
    "global/modals",
    "global/messages",
    "global/badges"
    
    

    Mm, so clean.

  13. @Mest and @HugoGiraudel Following Nils' @import example, isn't there a way to define the folder global/ once?

    @HugoGiraudel, loved the warning in your MQ mixin BTW :p

  14. @ricardozeo Nope. Dynamic imports are not available.

  15. @HugoGiraudel Thanks for that mixin rewrite. Very clean with a list.

  16. I figured I could share my media query mixin as well. This is how I use it:

      .foo {
      // outputs a regular min-width media query:
      @include mq(small) { color: red; }
    }
    
    .bar {
      // change direction to max-width:
      @include mq(medium, max-width) { color: red; }
    }
    
    .baz {
      // it doesn't have to be a keyword:
      @include mq(800px, min-height) { color: red; }
    }
    
    .bro {
      // you can set two conditions as well:
      @include mq(small, min-width, 1500px, max-width) { color: red; }
    }
    
    

    I think it's very convenient. Hope it's of use for somebody else!

    Here's a pen

  17. Good stuff - thanks for sharing.

  18. Hello, i have one question, which is torturing me: is "2 space indentation" and "2-space tab" the same things, or it's not? (which means that you really need to press space twice)

  19. very good!! great post, thanks for sharing! =)

  20. Very informative. Big, high-traffic sites recipes are really insightful, thanks for sharing !

  21. @chriscoyier are the CSS file sizes you list before or after gzip? Thanks!

  22. @if $point == exclusive-baby-bear { @media (max-width: 500px) { @content; } }

    Am I missing something or should that be min-width??

    :)

  23. @scrambledheads It's correct. max-width means "up to this wide" - so in creating styles "exclusively for baby bear", it's a max-width media query. Any size up to 500px but no more.

  24. @if $point == baby-bear { @media (max-width: 500px) { @content; } }

      @if $point == exclusive-baby-bear {
      @media (max-width: 500px) { @content; }
    }
    
    

    @chriscoyer yeah its more I was confused by baby-bear == exclusive-baby-bear (love the names)

  25. Not so great: SASS requires the breakpoint map to be on one lineā€¦ hard to overview this stuff. Anyway, I "fixed" the initial rework of Hugo here: http://sassmeister.com/gist/5fb3e3578f67ad906372 (see this GitHub issue concerning the problem)

  26. Brilliant write up and replies all, this gives me loads to think about so thanks!

    Just to throw my two-pence in on the mq breakpoint naming this is what I've been using now and seems to work for my projects:

      //media query breakpoints:
    $break-fast: 320px;
    $break-brunch: 480px;
    $break-elevenses: 568px;
    $break-lunch: 620px;
    $break-tea: 680px;
    $break-supper: 960px;
    $break-dinner : 1174px;
    
    
    

    but I think I might move these to a map and set things up like @HugoGiraudel & @HerrBertling have?

    Cheers!

Leave a Comment Markdown supported. Double-click names to add to comment.

You must be logged in to comment.