Medium’s CSS is actually pretty f***ing good.

fat
9 min readAug 28, 2014

--

I always believe that to be the best, you have to smell like the best, dress like the best, act like the best. When you throw your trash in the garbage can, it has to be better than anybody else who ever threw trash in the garbage can.

— Lil Wayne

The best at drawing pictures of myself

I’ve been meaning to write something about the CSS at Medium for a while because I’m not completely ashamed of it…

So how did that happen? What did we do differently? OMG, how can you do the same thing? Or learn from us, or something?

What follows are some notes on our CSS, the steps we’ve taken to get it to where it is, and the world we live in today.

In the beginning (some history)

Roughly 2 years ago I joined Obvious Corp. to work on the software application, medium.com (which, you’re ~hopefully~ reading on).

At the time, Medium had already gone through a series of “re-styles” (A re-style is where designers ruin your life by coming up with something prettier than they had previously come up with, resulting in you having to rewrite a bunch of CSS/LESS/SASS/etc). Because of these re-styles, there was a lot of built up cruft – the company was leaning pretty heavily on LESS’s language features, with page driven semantics, non-sprited/non-retina image assets, and other such things.

I dug through git and managed to pull out our old profile page implementation from back in 2012 ~shudder~. Check it out below and notice some of these unfortunate patterns:

  • everything nested under a .profile-page class, with zero reusable components (also ~almost~ everything is prefixed with .profile)
  • super generic variable names like @link-color which weren’t scoped to anything, but presumably only to be used in profile-page.less
  • deep nesting (.profile-page .profile-posts .home-post-summary .home-post-collection-image img is an actual selector being generated below — super rough on perf)
  • no image spriting
  • no z-index scale, no font scale, no color scale, no scalesss…
https://gist.github.com/fat/a4af78882d0003d2345e

The 1st Project: OMG Images

At the time, I had been working a lot on library code (Bootstrap, Ratchet, etc.) and sweating all the details, trying to write the best CSS humanly possible.

Medium’s CSS was clearly different. Not a neutral difference, but a soul crushing, shitty difference. And I wanted to fix that.

Looking at all there was to do, the first thing I tackled was images. I remember being pretty appalled that we weren’t doing any sort of spriting in 2012… accumulating hundreds of image assets like placeholders, arrows, icons, etc. in this crazy /img/ directory which was something like a graveyard for icons.

To get away from that world I did two things — first, I wrote a little CSS utility script called SUS (which we’re still using today and I’ve just open sourced here: https://github.com/medium/sus). This was largely written in IRC with the help of Guillermo Rauch, and is used to extract images from stylesheets and lazy load them in a separate, data-uri file. Not an original concept, just something simple that could be done to help us get to where we needed to go.

The second thing was Geoff Teehan and I sat down and created Medium’s first icon font. At the time, we largely had no idea what we were doing… but with the help of icomoon, a github blog post, and a few long nights, we were able to ship something ~pretty~ good to Medium. This was huge because it meant we could delete like 97% of the img folder, everything looked great on my retina MacBook Pro, and we were requesting significantly fewer resources.

The 2nd Project: Scales

The next major project for me was the z-index scale. Z-index is one of those things which can get out of hand super easily and has always driven me crazy. I didn’t want this to be the same at Medium.

Before this project began, it was very common to see one element with a z-index: 5, next to a sibling with a z-index: 1000000; and another sibling z-index: 1000001; and another z-index: 99999;.

The styles for these were spread all across the code base and there was no clear way of figuring out how to sort these.

So I took on the laborious task of manually auditing the entire codebase for z-index values. I then introduced a private scale (private to z-index.less) which could be used for z-indexing components (limiting it to 1-10). And finally, I moved all the actual assignments of this scale internal to z-index.less, so you could see how different components were situated relative to each other (which is actually pretty handy).

Below is the z-index file we are using on medium.com today.

https://gist.github.com/fat/1f6da6b3bd0311a1f8a0

Of course, this z-index scale was quickly followed by a color scale (variables for blacks, grays, whites, brand-colors, etc) and a type scale (variables for font-sizes, weights, letter-spacing, line-heights, etc.).

Also, you might notice the variable names have taken on some stricter, semantic value – more on that later…

The 3rd Project: New Style Guide

Not long after introducing scales to Medium, we started in on a big re-style code named “Cocoon”.

Cocoon deprecated several post templates (Medium originally had image templates and quote templates, not just a single article template) and also introduced post lists instead of post “cards.”

We took this as a chance to step back and write a new style guide for Medium.

This initial effort was shared between myself, Dave Gamache, Dustin Senos, and the talented Chris Erwin.

We took some time to really tighten up our thinking around writing production CSS/LESS at Medium — the major updates being:

  • Limit LESS to variables and mixins (no nesting, guards, extend, etc.) — while these language features ~can~ be powerful, we found that inexperienced LESS developers often got in trouble with them fairly easily. We also just preferred the visual aesthetic of pure CSS (and the consistency it afforded) and wanted to move our codebase as far in that direction as reasonable.
  • Classes and IDs are lowercase with words separated by a dash — this is how most of us had been writing CSS at the time with Bootstrap, Skeleton, Ratchet, etc. And we thought it made sense to do the same here. Another reason is it followed the naming conventions set forth by the CSS language itself, i.e. border-radius-top-left, etc.
  • Prefer components over page level styles — we wanted our front end to feel like library code, and began to break files like profile.less into smaller more focused files like button.less, dialog.less, tooltip.less, etc.

Here it is in its entirety:

https://gist.github.com/fat/b27700946c777adacdf4

It wasn’t perfect by any means, but it cleaned up a few basic ideas and got us headed towards what felt like the right direction.

Unfortunately, even after this style update, people were still struggling with when to make a component, when to make a subcomponent, etc… And we were getting the occasional unfocused, run-on classname like: .nav-on-light-background-button or .button-primary-sidebar-over-blur.

People weren’t prefixing classes at a page level anymore (which is great), but instead they were stringing together really long lists of arbitrary words separated by dashes. The evolution being: .button → .button-primary → .button-primary-dark → .button-primary-dark-container → .button-primary-dark-container-label, ad nauseam.

The 4th Project: Identifying the Future

About this time I started writing ~a lot~ about CSS internally at Medium with the lofty goal of it becoming the best in the world. I didn’t really know what that meant, but I knew our current direction just wasn’t working.

People were confused. And what’s worse, they were thinking they were writing CSS really well, when in reality they weren’t.

So, I started looking around — poking at frameworks, trying different tools, philosophies, talking to friends, talking to friends of friends, etc. I soon identified three major projects that I wanted to tackle in order to get our CSS to where I thought it needed to be.

  1. Introduce new CSS variable, mixin, and classname semantics to avoid run-on classnames and improve readability
  2. Move us off of LESS and onto Rework for more powerful mixin support and a syntax even closer to vanilla CSS
  3. Add tooling around CSS performance (load time, fps, layout time, etc) — to make it easier to track style changes and regressions over time

The 5th Project: Semantics

I wanted stricter semantics for our code base because for a team of our size I felt like it would be easier to have rules for us to lean on. And I’d rather have something a bit more complicated if that also meant people had to be more mindful when creating new CSS classes. At all costs, I wanted to avoid the run-on classname, or at least make it harder to create.

I began talking at length about it with Daryl Koopersmith and my good friend Nicolas Gallagher.

Nicolas and I have an interesting relationship in that Nicolas always tells me something, I say he’s wrong, call him names in french (Va te faire foutre, enculé), and dance around for a few weeks, before inevitably realizing he’s right, and taking his idea as my own.

This time was no different, and after a few late nights, I was eventually convinced of a style similar to the one Nicolas outlined in SUITCSS. Similar, but ~slightly~ better.

So I began by plagiarizing the hell out of Nicolas. I threw away our old style guide and copy pasted large portions of his, editing a few things here and there.

Eventually I came up with our current guide, which you can read in its entirety below, the main additions being:

  • .js- prefixed class names for elements being relied upon for javascript selectors
  • .u- prefixed class name for single purpose utility classes like .u-underline, .u-capitalize, etc.
  • Introduction of meaningful hypens and camelCase — to underscore the separation between component, descendant components, and modifiers
  • .is- prefixed classes for stateful classes (often toggled by js) like .is-disabled
  • New CSS variable semantics: [property]-[value]--[component]
  • Mixins reduced to polyfills only and prefixed with .m-
https://gist.github.com/fat/a47b882eb5f84293c4ed

It’s one thing to copy paste a style guide, it’s another to rewrite all of your CSS application.

Thankfully I was able to convince the company of the importance of this project, if only for my sanity, and was given 2½ weeks to rewrite all the CSS on Medium.com — which if you’re following along, is a fraction of the amount of time it took for us to fix CSS underlines.

That said, this rewrite was not only cathartic, it cleaned up loads of dead styles, tightened up and generalized implementations across the site, broke files out into smaller subcomponents, and to my surprise, only caused a handful of rollbacks.

Of course, refactoring CSS meant also refactoring our templates — adding more encapsulation and stricter semantics across the board. Now today, rather than having hundreds of one off <IMG> tags with random avatar classes for example, we have a single centralized avatar template, which accepts boolean options to trigger different modifiers like size, style, etc. This makes updating styles easier and implementation detail bugs much less frequent.

X Project: The future?

Undoubtedly we are in a better place then we were 2 years ago. Writing CSS at Medium is actually pretty pleasant and devs with different experience levels are contributing some pretty great stuff.

That said, the next CSS project will have to be around performance. As we continue to grow our story pages and push them to the next level, you can imagine how getting accurate, reliable measuring tools around layout and rendering performance is incredibly important… and kinda just sad that we don’t already have in 2014.

So that’s it. There’s more to do, but I’m feeling pretty good about it all at the moment. Which again, is better than usual I think.

Cheers if you made it to the bottom of this long boring post.

Thanks so much to Katie, Dave, Mark, Koop, Kristofer, Nicolas, and others for helping me write this thing (and fix Medium) without even probably realizing it ❤

PS

(If you enjoy this?! Consider reading my take on React and Redux:

--

--