[英] Medium 的 CSS 写得真他妈的好

4,795 阅读18分钟
原文链接: medium.com
掘金译文地址《[译] Medium 的 CSS 写得真他妈的好》,欢迎参加「掘金翻译计划」,翻译优质的技术文章。
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…
// Copyright 2012 The Obvious Corporation.

@column-margin: 9px;
@link-color: rgb(78, 78, 78);
@link-hover-color: rgb(160, 160, 160);

.profile-page {
  margin: 0 0 0 55px;
  width: auto;
  font-family: helvetica, arial, sans-serif;
  line-height: 1.2em;

  .profile-left {
    width: 925px;
    float: left;

    h1 {
      height: 55px;
      line-height: 55px;
      text-transform: uppercase;
      font-size: 15px;
      font-weight: 700;
      padding-left: 30px;
    }

    .profile-details {
      background-color: rgb(47, 47, 47);
      padding: 47px 37px;
      position: relative;

      .profile-settings-link {
        position: absolute;
        width: 48px;
        height: 48px;
        right: 37px;
        top: 22px;
        text-indent: 1000px;
        overflow: hidden;
        background: url(/img/placeholder-gray.png) no-repeat center center;
      }
      .profile-image {
        float: left;
        margin-right: 47px;
        img {
          width: 128px;
          height: 128px;
          .border-radius(64px);
        }
      }

      .profile-name {
        font-family: georgia, "Times New Roman", serif;
        font-size: 32px;
        font-style: italic;
        font-weight: 400;
        color: white;
        text-shadow: 0 1px 0 rgba(0, 0, 0, 0.6);
        text-transform: capitalize;
      }

      .profile-bio {
        margin-left: 175px; // = 128 + 47
        font-size: 16px;
        line-height: 1.2em;
        font-weight: 400;
        color: rgb(224, 224, 224);
      }
    }

    .profile-stats {
      .clearfix;
      height: 66px;
      line-height: 66px;
      padding-left: 28px;
      border: 1px solid rgb(211, 211, 211);
      border-top: none;
      background: rgb(245, 245, 245);

      .profile-stats-label {
        float: left;
        height: 66px;
        line-height: 66px;
        text-transform: uppercase;
        font-size: 15px;
        font-weight: 700;
        color: rgb(77, 77, 77);
      }

      .profile-stats-item {
        width: 139px;
        height: 66px;
        line-height: 66px;
        border-left: 1px solid rgb(211, 211, 211);
        float: right;
        text-align: center;
        font-family: georgia, "Times New Roman", serif;
        font-size: 14px;
        font-weight: 700;
        color: rgb(153, 153, 153);
        background: transparent url(/img/placeholder-gray.png) no-repeat center 10px;
        padding-top: 16px;
        .box-sizing(border-box);
      }
    }

    .profile-posts {
      border: 1px solid rgb(211, 211, 211);
      border-top: none;
      background: white;

      .profile-posts-label {
        float: left;
        margin-left: 28px;
        height: 66px;
        line-height: 66px;
        text-transform: uppercase;
        font-size: 15px;
        font-weight: 700;
        color: rgb(77, 77, 77);
      }

      // TODO(sho): For now, we're just calling the homepage template directly, which means
      // that we get some .home-XXX styles in our layout. Once we move that to a common place
      // we will fix this.
      .home-post-summary {
        .box-sizing(border-box);
        padding: 16px 28px;
        border-top: 1px solid rgb(244, 244, 244);

        .home-post-collection-image {
          margin-bottom: 0;

          img {
            border: 1px solid rgb(240, 240, 240);
          }
        }
      }
    }
  }

  .profile-right {
    float: left;
    margin-left: 13px;
    width: 220px;

    h1 {
      .box-sizing(border-box);
      width: 220px;
      line-height: 55px;
      height: 60px;
      padding-left: 9px;
      border-bottom: 5px solid rgb(42, 42, 42);
      text-transform: uppercase;
      font-size: 15px;
      font-weight: 700;
    }

    .profile-containers {
      .box-sizing(border-box);
      border-top: none;
      border: 1px solid rgb(211, 211, 211);
      background: white;
      width: 220px;
    }
  }

  a {
    color: @link-color;
    font-weight: 600;
    &:hover {
      color: @link-hover-color;
    }
  }
}

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: 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.

// Copyright 2014 A Medium Corporation
//
// z-index.less
// Medium.com's z-index scale. Z-index values should always be defined in z-index.less. This
// allows us to at a glance determine relative layers of our application and prevents bugs
// arrising from arbitrary z-index values. Do not edit the z-index scale! Only add application
// scoped z-index values.


// Z-Index Scale (private vars)
// --------------------------------------------------
@zIndex-1:   100;
@zIndex-2:   200;
@zIndex-3:   300;
@zIndex-4:   400;
@zIndex-5:   500;
@zIndex-6:   600;
@zIndex-7:   700;
@zIndex-8:   800;
@zIndex-9:   900;
@zIndex-10: 1000;


// Z-Index Applications
// --------------------------------------------------
@zIndex-1--screenForeground:        @zIndex-1;
@zIndex-1--followUpVisibility:      @zIndex-1;
@zIndex-1--prlWelcome:              @zIndex-1;
@zIndex-1--appImageDropdown:        @zIndex-1;
@zIndex-1--surfaceUnder:            @zIndex-1;
@zIndex-1--blockGroup:              @zIndex-1;

@zIndex-2--surfaceOver:             @zIndex-2;
@zIndex-2--imagePickerControl:      @zIndex-2;
@zIndex-2--collectionCardButton:    @zIndex-2;
@zIndex-2--blockGroupButtonGroup:   @zIndex-2;
@zIndex-2--blockGroupFocused:       @zIndex-2;
@zIndex-2--blockGroupOverlay:       @zIndex-2;

@zIndex-3--caption:                 @zIndex-3;
@zIndex-3--blockInsertControl:      @zIndex-3;

@zIndex-5--figureOverlay:           @zIndex-5;
@zIndex-5--highlightMenu:           @zIndex-5;
@zIndex-5--metabar:                 @zIndex-5;
@zIndex-5--profileAvatar:           @zIndex-5;
@zIndex-5--noteRecommendations:     @zIndex-5;
@zIndex-5--collectionLogo:          @zIndex-5;

@zIndex-6--matterLogo:              @zIndex-6;
@zIndex-6--editorSidebar:           @zIndex-6;
@zIndex-6--navOverlay:              @zIndex-6;

@zIndex-7--nav:                     @zIndex-7;

@zIndex-8--transitioningContainer:  @zIndex-8;
@zIndex-8--panel:                   @zIndex-8;
@zIndex-8--butterBar:               @zIndex-8;
@zIndex-8--loadingBar:              @zIndex-8;
@zIndex-8--zoomOverlay:             @zIndex-8;

@zIndex-9--zoomOverlayTarget:       @zIndex-9;
@zIndex-9--navOverlayTouch:         @zIndex-9;
@zIndex-9--overlay:                 @zIndex-9;
@zIndex-9--dialog:                  @zIndex-9;
@zIndex-9--tooltip:                 @zIndex-9;

@zIndex-10--dev:                    @zIndex-10;


// Z-Index Mobile-Notes
// --------------------------------------------------
@zIndex-5--mobileNotesOverlay:    @zIndex-5;
@zIndex-6--mobileNotesHighlight:  @zIndex-6;
@zIndex-7--mobileNotesContainer:  @zIndex-7;
@zIndex-8--mobileNotesDismiss:    @zIndex-8;

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:

Copyright 2013 the Obvious Corporation

LESS Coding Guidelines

The Obvious Corporation uses LESS as its CSS preprocessor. It's helpful to become familiar with its dynamic behavior before reading these guidelines.

Naming Conventions

Classes and IDs are lowercase with words separated by a dash:

Right: ```css .user-profile {} .post-header {}

top-navigation {}


**Wrong:**
```css
.userProfile {}
.postheader {}
#top_navigation {}

Image file names are lowercase with words separated by a dash:

Right: icon-home.png

Wrong: iconHome.png icon_home.png iconhome.png

Image file names are prefixed with their usage.

Right: icon-home.png bg-container.jpg bg-home.jpg sprite-top-navigation.png

Wrong: home-icon.png container-background.jpg bg.jpg top-navigation.png

IDs vs. Classes

You should almost never need to use IDs. Broken behavior due to ID collisions are hard to track down and annoying.

Color Units

When implementing feature styles, you should only be using color variables provided by colors.less.

When adding a color variable to colors.less, using RGB and RGBA color units are preferred over hex, named, HSL, or HSLA values.

Right: css rgb(50, 50, 50); rgba(50, 50, 50, 0.2);

Wrong: ```css

FFF;

FFFFFF;

white; hsl(120, 100%, 50%); hsla(120, 100%, 50%, 1); ```

z-index scale

Please use the z-index scale defined in z-index.less. @z-index-1 - @z-index-9 are provided. @z-index-9 is the kill -9 of our z-index scale and really should never be used.

Font Weight

With the additional support of web fonts font-weight plays a more important role than it once did. Different font weights will render typefaces specifically created for that weight, unlike the old days where bold could be just an algorithm to fatten a typeface. Obvious uses the numerical value of font-weight to enable the best representation of a typeface. The following table is a guide:

Raw font weights should not be specified. Instead, use the appropriate font mixin: .wf-sans-i7, .wf-sans-n7, etc.

The suffix defines the weight and style:

n = normal
i = italic
4 = normal font-weight
7 = bold font-weight

Refer to type.less for type size, letter-spacing, and line height. Raw sizes, spaces, and line heights should be avoided outside of type.less.

ex:

@type-micro
@type-smallest
@type-smaller
@type-small
@type-base
@type-large
@type-larger
@type-largest
@type-jumbo

See Mozilla Developer Network — font-weight for further reading.

Componentizing

Always look to abstract components. Medium has a very strong, very consistent style and the reuse of components across designs helps to improve this consistency at an implementation level.

A name like .homepage-nav limits its use. Instead think about writing styles in such a way that they can be reused in other parts of the app. Instead of .homepage-nav, try instead .nav or .nav-bar. Ask yourself if this component could be reused in another context (chances are it could!).

Components should belong to their own less file. For example, all general button definitions should belong in buttons.less.

Name-spacing

Name-spacing is great! But it should be done at a component level – never at a page level.

Also, namespacing should be made at a descriptive, functional level. Not at a page location level. For example, .profile-header could become .header-hero-unit.

Wrong:

.nav,
.home-nav,
.profile-nav,

Right:

.nav,
.nav-bar,
.nav-list

Style Scoping

Medium pages should largely be reusing the general component level styles defined above. Page level name-spaces however can be helpful for overriding generic components in very specific contexts.

Page level overrides should be minimal and under a single page level class nest.

.home-page {
  .nav {
    margin-top: 10px;
  }
}

Nesting

Don't nest. Ever.

Nesting makes it harder to tell at a glance where css selector optimizations can be made.

Wrong:

.list-btn {
  .list-btn-inner {
    .btn {
      background: red;
    }
    .btn:hover {
      .opacity(.4);
    }
  }
}

Right:

.list-btn .btn-inner {
  background: red;
}
.list-btn .btn-inner:hover {
  .opacity(.4);
}

Spacing

CSS rules should be comma seperated but live on new lines:

Right: css .content, .content-edit { … }

Wrong: css .content, .content-edit { … }

CSS blocks should be seperated by a single new line. not two. not 0.

Right: css .content { … } .content-edit { … }

Wrong: ```css .content { … }

.content-edit { … } ```

Comments

Use Less style comments to annotate styles because they are compiled out. Use them to seperate logical groups of styles within a document. Always use the following style comment:

Right:

// Title of section // --------------------------------------------------


**Wrong:**
```css

/* Title of section
 **********************/

Quotes

Quotes are optional in CSS and LESS. Obvious chooses to use quotes as it is visually clearer that the string is not a selector or a style property.

Right: css background-image: url("/img/you.jpg"); font-family: "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial;

Wrong: css background-image: url(/img/you.jpg); font-family: Helvetica Neue Light, Helvetica Neue, Helvetica, Arial;

Performance

Specificity

Although in the name (cascading style sheets) cascading can introduce unnecessary performance overhead for applying styles. Take the following example:

ul.user-list li span a:hover { color: red; }

Styles are resolved during the renderer's layout pass. The selectors are resolved right to left, exiting when it has been detected the selector does not match. Therefore, in this example every a tag has to be inspected to see if it resides inside a span and a list. As you can imagine this requires a lot of DOM walking and and for large documents can cause a significant increase in the layout time. For further reading checkout: developers.google.com/speed/docs/…

If we know we want to give all a elements inside the .user-list red on hover we can simplify this style to:

.user-list a:hover { color: red; }

If we want to only style specific a elements inside .user-list we can give them a specific class:

.user-list .link-primary:hover { color: red; }

Mixins

Make sure to take into consideration the output of using LESS' powerful mixins. They are best used for grouping browser-specific code or as powerful ways to contain functionality. They are not a good way to add additional styles to an element as you will be sending duplicate styles across the wire.

Wrong:

Click me
.btn-primary {
  color: red;
  background: black;
  width: 120px;
  height: 60px
  border-radius: 6px
}
.btn-secondary {
  .btn-primary;
  color: green;
}
.btn-disabled {
  .btn-primary;
  color: gray;
  background: darkgray;
}

Generated Output: css .btn-primary { color: red; background: black; width: 120px; height: 60px border-radius: 6px } .btn-secondary { color: red; background: black; width: 120px; height: 60px border-radius: 6px color: green; } .btn-disabled { color: red; background: black; width: 120px; height: 60px border-radius: 6px color: gray; background: darkgray; }

Right:

Click me
.btn {
  width: 120px;
  height: 60px
  border-radius: 6px
}
.btn-primary {
  color: red;
  background: black;
}
.btn-disabled {
  color: gray;
  background: darkgray;
}

Generated Output: css .btn { width: 120px; height: 60px border-radius: 6px } .btn-primary { color: red; background: black; } .btn-disabled { color: gray; background: darkgray; }

Vendor-Prefixes

Encapsulate vendor-prefixes into reusable parameterized LESS mixins:

Right:

.user-page-avatar {
  .transition(width .2s ease-in);
}

.transition(@transition) {
  -webkit-transition: @transition;
     -moz-transition: @transition;
      -ms-transition: @transition;
       -o-transition: @transition;
          transition: @transition;
}

Wrong:

.user-page-avatar {
  -webkit-transition: width .2s ease-in;
     -moz-transition: width .2s ease-in;
      -ms-transition: width .2s ease-in;
       -o-transition: width .2s ease-in;
          transition: width .2s ease-in;
}

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-

LESS Coding Guidelines

Medium uses a strict subset of LESS for style generation. This subset includes variables and mixins, but nothing else (no nesting, etc.).

Medium's naming conventions are adapted from the work being done in the SUIT CSS framework. Which is to say, it relies on structured class names and meaningful hyphens (i.e., not using hyphens merely to separate words). This is to help work around the current limits of applying CSS to the DOM (i.e., the lack of style encapsulation) and to better communicate the relationships between classes.

Table of contents

JavaScript

syntax: js-

JavaScript-specific classes reduce the risk that changing the structure or theme of components will inadvertently affect any required JavaScript behaviour and complex functionality. It is not neccesarry to use them in every case, just think of them as a tool in your utility belt. If you are creating a class, which you dont intend to use for styling, but instead only as a selector in JavaScript, you should probably be adding the js- prefix. In practice this looks like this:


Again, JavaScript-specific classes should not, under any circumstances, be styled.

Utilities

Medium's utility classes are low-level structural and positional traits. Utilities can be applied directly to any element; multiple utilities can be used together; and utilities can be used alongside component classes.

Utilities exist because certain CSS properties and patterns are used frequently. For example: floats, containing floats, vertical alignment, text truncation. Relying on utilities can help to reduce repetition and provide consistent implementations. They also act as a philosophical alternative to functional (i.e. non-polyfill) mixins.

{$text}

u-utilityName

Syntax: u-

Utilities must use a camel case name, prefixed with a u namespace. What follows is an example of how various utilities can be used to create a simple structure within a component.

Components

Syntax: [--modifierName|-descendantName]

Component driven development offers several benefits when reading and writing HTML and CSS:

  • It helps to distinguish between the classes for the root of the component, descendant elements, and modifications.
  • It keeps the specificity of selectors low.
  • It helps to decouple presentation semantics from document semantics.

You can think of components as custom elements that enclose specific semantics, styling, and behaviour.

ComponentName

The component's name must be written in camel case.

.myComponent { /* … */ }

componentName--modifierName

A component modifier is a class that modifies the presentation of the base component in some form. Modifier names must be written in camel case and be separated from the component name by two hyphens. The class should be included in the HTML in addition to the base component class.

/* Core button */
.btn { /* … */ }
/* Default button style */
.btn--default { /* … */ }

componentName-descendantName

A component descendant is a class that is attached to a descendant node of a component. It's responsible for applying presentation directly to the descendant on behalf of a particular component. Descendant names must be written in camel case.


  
  

componentName.is-stateOfComponent

Use is-stateName for state-based modifications of components. The state name must be Camel case. Never style these classes directly; they should always be used as an adjoining class.

JS can add/remove these classes. This means that the same state names can be used in multiple contexts, but every component must define its own styles for the state (as they are scoped to the component).

.tweet { /* … */ }
.tweet.is-expanded { /* … */ }

Variables

Syntax: -[--componentName]

Variable names in our CSS are also strictly structured. This syntax provides strong associations between property, use, and component.

The following variable defintion is a color property, with the value grayLight, for use with the highlightMenu component.

@color-grayLight--highlightMenu: rgb(51, 51, 50);

Colors

When implementing feature styles, you should only be using color variables provided by colors.less.

When adding a color variable to colors.less, using RGB and RGBA color units are preferred over hex, named, HSL, or HSLA values.

Right: css rgb(50, 50, 50); rgba(50, 50, 50, 0.2);

Wrong: ```css

FFF;

FFFFFF;

white; hsl(120, 100%, 50%); hsla(120, 100%, 50%, 1); ```

z-index scale

Please use the z-index scale defined in z-index.less.

@zIndex-1 - @zIndex-9 are provided. Nothing should be higher then @zIndex-9.

Font Weight

With the additional support of web fonts font-weight plays a more important role than it once did. Different font weights will render typefaces specifically created for that weight, unlike the old days where bold could be just an algorithm to fatten a typeface. Obvious uses the numerical value of font-weight to enable the best representation of a typeface. The following table is a guide:

Raw font weights should be avoided. Instead, use the appropriate font mixin: .font-sansI7, .font-sansN7, etc.

The suffix defines the weight and style:

N = normal
I = italic
4 = normal font-weight
7 = bold font-weight

Refer to type.less for type size, letter-spacing, and line height. Raw sizes, spaces, and line heights should be avoided outside of type.less.

ex:

@fontSize-micro
@fontSize-smallest
@fontSize-smaller
@fontSize-small
@fontSize-base
@fontSize-large
@fontSize-larger
@fontSize-largest
@fontSize-jumbo

See Mozilla Developer Network — font-weight for further reading.

Line Height

Type.less also provides a line height scale. This should be used for blocks of text.

ex:

@lineHeight-tightest
@lineHeight-tighter
@lineHeight-tight
@lineHeight-baseSans
@lineHeight-base
@lineHeight-loose
@lineHeight-looser

Alternatively, when using line height to vertically center a single line of text, be sure to set the line height to the height of the container - 1.

.btn {
  height: 50px;
  line-height: 49px;
}

Letter spacing

Letter spacing should also be controlled with the following var scale.

@letterSpacing-tightest
@letterSpacing-tighter
@letterSpacing-tight
@letterSpacing-normal
@letterSpacing-loose
@letterSpacing-looser
````


## Polyfills

mixin syntax: `m-`

At Medium we only use mixins to generate polyfills for browser prefixed properties.


An example of a border radius mixin:

```css
.m-borderRadius(@radius) {
  -webkit-border-radius: @radius;
     -moz-border-radius: @radius;
          border-radius: @radius;
}

Formatting

The following are some high level page formatting style rules.

Spacing

CSS rules should be comma seperated but live on new lines:

Right: css .content, .content-edit { … }

Wrong: css .content, .content-edit { … }

CSS blocks should be seperated by a single new line. not two. not 0.

Right: css .content { … } .content-edit { … }

Wrong: css .content { … } .content-edit { … }

Quotes

Quotes are optional in CSS and LESS. We use double quotes as it is visually clearer that the string is not a selector or a style property.

Right: css background-image: url("/img/you.jpg"); font-family: "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial;

Wrong: css background-image: url(/img/you.jpg); font-family: Helvetica Neue Light, Helvetica Neue, Helvetica, Arial;

Performance

Specificity

Although in the name (cascading style sheets) cascading can introduce unnecessary performance overhead for applying styles. Take the following example:

ul.user-list li span a:hover { color: red; }

Styles are resolved during the renderer's layout pass. The selectors are resolved right to left, exiting when it has been detected the selector does not match. Therefore, in this example every a tag has to be inspected to see if it resides inside a span and a list. As you can imagine this requires a lot of DOM walking and and for large documents can cause a significant increase in the layout time. For further reading checkout: developers.google.com/speed/docs/…

If we know we want to give all a elements inside the .user-list red on hover we can simplify this style to:

.user-list > a:hover {
  color: red;
}

If we want to only style specific a elements inside .user-list we can give them a specific class:

.user-list > .link-primary:hover {
  color: red;
}

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 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 ❤