Skip to content

Instantly share code, notes, and snippets.

@nominalaeon
Created April 26, 2017 17:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nominalaeon/c39c768ed66c81fb9b377cbada8e2a0c to your computer and use it in GitHub Desktop.
Save nominalaeon/c39c768ed66c81fb9b377cbada8e2a0c to your computer and use it in GitHub Desktop.

  1. Ground rules
  2. Organizing
    1. Compiler methods
      1. @extend
      2. @include
    2. Attributes
    3. Pseudo-classes
    4. Pseudo-elements
    5. Elements & Classes
    6. ID's
  3. Naming
  4. Nesting
  5. FAQ You 7. "I've got my own system. Hasn't failed me yet." 8. "Sass in not the same as SCSS"

Ground rules

Ground rule #1: When I say Sass, I'm talking about Sassy CSS (SCSS). Sass™ was the CSS compiler for last-gen CSS. SCSS, however, is CSS3-friendly and needed to be differentiated during the dark times before IE had caught up with the rest of the world. Those times are basically over, it's okay to say "Sass" in reference to "SCSS".

Ground rule #2: When I say Sass, I'm talking about CSS compilers in general. I'm not going to cover any Sass concept that doesn't exist in Sass, SCSS, Less, Stylus, or whatever. Anything I use Sass syntax to illustrate is a Google search away from becoming the syntax you require.

Ground rule #3: If you're not writing CSS with a pre-processor, stop all the things and go install one. They fit any project and will make your life the opposite of the living hell it is right now.

Organizing

All of the items nested inside of an element are more readable if you follow the same pattern every time. Here's an overview for how I recommend you order these items, and below we'll go over some reasoning for each one.

element {
    @extend .classname;         /** 1. Compiler method @extend */
    @include mixin-name();      /** 2. Compiler method @include */
                                
    background-color: $blue;
    position: relative;
    width: auto;                /** 3. Attributes */
    
    &:hover { ... }             /** 4. Pseudo-classes */

    &::before { ... }           /** 5. Pseudo-elements */

    &.selector-class { ... }    /** 6. Classes */

    a { ... }
    h2 { ... }
    span { ... }                /** 7. Nested Elements */

    .selector-class { ... }     /** 8. Nested Classes */
}

Line-breaks: I'm a huge fan of white space. Some people are comfortable jamming everything together, but a simple double line-break is an easy way to visually separate concepts.

Compiler methods

@extend

This is Sass's way of saying, "Copy all the attributes of this selector and paste it right here." It's like a mixin without any control over the outcome or a variable that gives you lots of CSS all at once. Most importantly, @extend is perfectly communicative. Here's a quick example:

.lipstick {
    font-style: italic;
    font-weight: 600;
    text-align: center;
}
.pig {
    @extend .lipstick;
}

/** .pig compiles as
.pig {
    font-style: italic;
    font-weight: 600;
    text-align: center;
}
*/

Keep @extend declarations at the very top so their specificity can be overpowered with attributes defined afterward.

@include

This is how we invoke a mixin. They often take arguments, but they can just as easily be used as factories to pump out blobs of attributes. Ideally they are used with arguments to return unique results for each caller's needs. If you're needing a way to hand over just a bunch of static attributes, I would argue that @extend is the more expressive way to do so.

@mixin alignment($direction) {
    float: $direction;
    text-align: $direction;
}
.pig {
    @extend .lipstick;
    @include alignment(left);
}

/** .pig compiles as
.pig {
    font-style: italic;
    font-weight: 600;
    text-align: center;
    float: left;
    text-align: left; /** value from @include will override @extend */
}
*/

Keep @include declarations second so they can overwrite any @extend declarations but still be in a position where their specificity can be overpowered.

Attributes

Alphabetize your attributes. Every modern text editor has extensions or plug-ins or even built in functionality to help if you're not already in the habit of alphabetizing.

element {
    background-color: $blue;
    border: 1px solid $red;
    border-top-color: $green;
    border-right-width: 2px;
    border-bottom-color: $yellow;
    border-bottom-width: 2px;
    border-left-color: $green;
    display: inline;
    height: auto;
    min-height: 100px;
    max-height: 200px;
    margin: 2px 4px 6px 8px;
    padding: 0;
    padding-right: 20px;
    padding-bottom: 10px;
    position: absolute;
    top: 0; right: 0; bottom: 0; left: 0; // when space allows, position directions can be inlined.
    vertical-align: top;
    width: auto;
    max-width: 100%;
}

You'll have noticed there are a couple exceptions to alphabetic ordering:

Position attributes: The shorthand rule for positioning (top, right, bottom, left) is tricky to learn and remember at first. It can help to think of this like a compass (north, east, south, west). Order positioning properties in a way to facilitate learning and retaining that convention because it can feel very counter-intuitive at first.

element {
    margin: 2px 4px 6px 8px; // top, right, bottom, left
}

Min/Max attributes: In a long block of properties, having "min" and "max" in the M's instead of with their respective "height"-or-"width" counterpart is confusing. They should go directly below their respective dimension property. I prefer to order them min-then-max, because whenever I say it in my head, "min/max" is the first thing I hear.

element {
    height: auto;
    min-height: 100px;
    max-height: 200px;
}

Pseudo-classes

Pseudo-classes are selectors for an element's state. As long as you're defining your pseudo-classes after the extends, includes, and attributes, you're golden. The order of these are not concrete, sometimes you'll need a :hover to be stronger than a :focus and vice versa, so it's really case by case. There aren't an overwhelming amount of pseudo-classes available so alphabetize when possible, otherwise order as needed to achieve the desired specificity.

See the full, official, curated-by-someone-else list of pseudo-classes at Mozilla Developer Network.

Pseudo-elements

You'll often be working with a max of two of these, ::before and ::after. I prefer to put ::before ahead of ::after but you'd have a hard time mucking up readability between these two. Keep their declarations after the pseudo-classes and that's good enough.

See the full, official, curated-by-someone-else list of pseudo-elements at Mozilla Developer Network.

Elements & Classes

Alphabetize your selectors. This one is tougher to swallow, but the best stuff often is. It's always tempting to group selectors together, letting their hierarchy be a self-serving table of contents. And aye, that will work. For a time. But, lying in your beds, years from now, would you not trade all of your days from then to now for one chance, just one chance, to enforce an alphabetized list of elements and class names before they became too many to manage!

* Please note, this convention is not at all meant to replace splitting CSS into single-responsibility files and directories. If you disagree with this approach, please refer to the FAQ.

ID's

You might have noticed that I forgot to mention ID selectors. If no one has ever sat you down and given you the talk, allow me the honor. ID's are for form element identification. For everything else, there's class names. Selecting DOM elements for manipulation with CSS (or JavaScript, I would half-heartedly argue and concede as soon as I faced any opposition) is a pretty good indication that you need to revisit your naming conventions...

Naming

Use BEM naming conventions

Body, Element, Modifier (BEM) is a well-established convention that helps reduce specificity conflicts. For a pre-processor like Sass, this means reducing the need to nest selectors which, above everything else, improves code readability.

Let's use an old-school example. Here's some HTML followed by its Sass:

<div class="pig">
    <div class="leg left"></div>
    <div class="leg right"></div>
</div>

.pig {
    .leg {
        &.left {
            float: left;
        }
    }
}

That Sass compiles to .pig .leg.left, a Frankenstein selector with a specificity of three! Good luck overriding that without !important. If we trade off using a little more verbosity in the markup, the Sass can be distilled to:

<div class="pig">
    <div class="pig__leg pig__leg--left"></div>
    <div class="pig__leg pig__leg--right"></div>
</div>

.pig__leg--left {
    float: left;
}

Now we have .pig__leg--left handling the unique positioning of the pig's left leg. The name's a little longer but it's 1) more readable, 2) rocking a specificity of one, 3) not hiding in a Sass nest, and 4) can be easily overridden.

"If you follow strict BEM conventions, you will be able to update and add to your CSS in the future with the full confidence that your changes will not have side effects."

There are other, equally respected and established conventions, and, while my preference is BEM, a project benefits from having any of them in place. A project that's dedicated to a naming-convention framework reduces conflict and improves consistency.

Nesting

Only nest when necessary

One of the main benefits of using BEM is moving away from adding more than one selector for any element. However, the opposite side of over-nesting is over-BEM'ing. It takes some getting used to but the point of BEM is to reduce nesting, not eliminate it. Not every element needs a class name, it's okay to use element selectors and even unrelated class selectors inside of a defined element. For example:

<div class="pig">
    <h2>Some pig.</h2>

    <div class="pig__leg pig__leg--left"></div>
    <div class="pig__leg pig__leg--right"></div>
    
    <div class="saddle">
        <h3>Yee-haw!</h3>
    </div>
</div>

.pig {
    h2 {
        color: $pink;
    }

    .saddle {
        background-color: $brown;

        h3 {
            color: $white;
        }
    }
}

Like anything else, BEM's meant to be used in moderation. Even with the .saddle class nested in the .pig, the code is infinitely more manageable than it was with the nested .left .leg. Additionally, with .saddle nested like this, it now has a specificity of two which is strong enough to override other declarations to maintain its unique styling when it's on the .pig but weak enough that it can be overridden with a modifier:

.pig--dirty {
    .saddle {
        display: none;
    }
}

Protip: Don't put your saddle on a dirty pig.

FAQ You

"I've got my own system. Hasn't failed me yet."

I've got my own system

There's an outside chance that you're one of the dozens of developers that have never alphabetized their attributes and right now you're thinking "Not broke? Don't fix." There are a couple explanations for this response. 1) Your attribute blocks are still small enough that readability isn't effected or 2) you're totally in tune with the piles of clutter you've created for yourself. Both explanations are arguments against themselves. They are short-sighted and would be hard to say out-loud to your boss, but I totally dare you to anyway.

I will concede that there is value in personal and team habits. If you have your own system and it truly hasn't failed you yet, kudes to you. I recommend you reconsider the code's readability; make sure your way is lowering your bus factor and creating an easy on-boarding process for new devs. If it is, then great. Mad props, outlier.

Mad props, outlier.

"Sass in not the same as SCSS"

If you want to quibble over the whole saying "Sass" is the same as saying "SCSS" thing, prove your point by installing a Sass pre-processor and use .sass files.

Yeah, "who does that anymore", right?

Right.


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment