Skip to content
Z&er edited this page Mar 15, 2017 · 6 revisions

CSS Guidelines at TMW Unlimited by @mrmartineau

Contents

  1. CSS
    1. General CSS Principles
    2. Syntax and formatting
      1. Styleguide
      2. CSS Naming Scheme
      3. Linting
    3. Indenting
    4. OOCSS
    5. Typography
    6. Reset vs Normalisation
    7. Comments
    8. Specificity, IDs and classes
    9. Conditional Stylesheets
    10. !important
    11. Magic Numbers and absolutes
    12. Images
    13. Debugging
    14. Preprocessors
    15. Responsive Design
  2. Sass
    1. Nesting
    2. @extend
    3. Module Naming Helper Mixins

General CSS Principles

  • Every time you write inline styles in your markup, a front-end developer somewhere dies - whether it's in a style tag or directly in the markup. Don't do it.
  • Add CSS through external files, minimizing the number of files, if possible. CSS should always be included in the <head> of the document.
  • Use the <link> tag to include, never @import.
  • Ensure markup and style stays separate (some style classes are allowed, e.g imageReplace etc). Only use style only markup if you absolutely have to (e.g extra wrapping elements); consider :before and :after CSS pseudo-elements if styles are not 100% necessary.
    • This rule can potentially be broken when dealing with a React project, but will be decided upon on a project, by project basis.

Syntax and formatting

  • Use multi-line CSS declarations. This helps with version control (diff-ing single line CSS can be a nightmare). Group CSS declarations by type - keeping font related styling together, layout styling together etc - and ordered by relevance, not alphabetized.
  • All CSS rules should have a space after the selector colon and a trailing semi-colon.
  • Selectors should be specified using a simplified version of BEM:

Styleguide

  • 1 tab indent
  • ideally, 80-characters wide lines
  • properly written multi-line CSS rules
  • meaningful use of whitespace

CSS Naming scheme

Kickoff uses a bespoke naming scheme for classnames, inspired loosely by the BEM naming scheme.

This obviously isn’t compulsory to use in your own Kickoff projects, but is documented here as guidance, and is what we use across our Kickoff projects.

/* Descriptors use camel-casing if more than one word: e.g. twoWords */
.form {
  ...
}

/* ========= */

/* Child elements use single hyphens: - */
.form-controlGroup {
  ...
}

/* ========= */

/* Modifier element use a double hyphen: -- */
.form {
  ...
}
.form--horizontal {
  ...
}

/* ========= */

/* Element state: .is- or .has- */
.is-active {
  ...
}

/* ========= */

/* Sass variables use dash-case */
a {
  color: $color-primary;
}

Linting

We use Stylelint and follow the stylelint-config-standard

The following code is a good example of what your CSS should look like when linted with Stylelint; it can be applied to both CSS and your un-processed Sass:

/**
 * Multi-line comment
 */

// Each selector should *always* be on their own line
.selector-1,
.selector-2,
.selector-3[type="text"] {
  background: linear-gradient(#fff, rgba(0, 0, 0, 0.8));
  box-sizing: border-box;
  display: block;
  color: #333;
}

.selector-a,
.selector-b:not(:first-child) {
  padding: 10px !important;
  top: calc(calc(1em * 2) / 3);
}

// Items with only property can be on a single line
.selector-x { width: 10%; }
.selector-y { width: 20%; }
.selector-z { width: 30%; }

/* Single-line comment */

@media (min-width >= 60em) {
  .selector {
    /* Flush to parent comment */
    transform: translate(1, 1) scale(3);
  }
}

@media (orientation: portrait), projection and (color) {
  .selector-i + .selector-ii {
    background: color(rgb(0, 0, 0) lightness(50%));
    font-family: helvetica, "arial black", sans-serif;
  }
}

/* Flush single line comment */
@media
  screen and (min-resolution: 192dpi),
  screen and (min-resolution: 2dppx) {
  .selector {
    background-image:
      repeating-linear-gradient(
        -45deg,
        transparent,
        #fff 25px,
        rgba(255, 255, 255, 1) 50px
      );
    margin: 10px;
    margin-bottom: 5px;
    box-shadow:
      0 1px 1px #000,
      0 1px 0 #fff,
      2px 2px 1px 1px #ccc inset;
    height: 10rem;
  }

  /* Flush nested single line comment */
  .selector::after {
    content: '';
    background-image: url(x.svg);
  }
}
  • Use shorthand when specifying multiple values. Remember longhand can be shorter for single values.
  • Multi-attribute selectors should go on separate lines.
  • Don't over qualify class or ID selectors. Leads to specificity issues further down the line.
// Bad
div.content {}

// Good
.content {}
  • 0 values require no units
// Good
.bar,
.foo[href="bar"] {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;

  padding: 10px 0 0 0;
  margin: 10px 0;

  background: red;
  border-radius: 10px;
  -moz-border-radius: 10px;
}
  • Border reset
// Bad
.foo {
  border: none;
}

// Good
.foo {
  border: 0;
}

OOCSS

When building components, or modules, try and keep a 'DRY', 'OO' frame of mind.

Instead of building dozens of unique components, try and spot repeated design patterns and abstract them; build these skeletons as base 'objects' and then peg classes onto these to extend their styling for more unique circumstances.

If you have to build a new component split it into structure and skin; build the structure of the component using very generic classes so that we can reuse that construct and then use more specific classes to skin it up and add design treatments.

Read:

Typography

@font-face should be used for font replacement. Where this is an issue, look to use tools such as TypeKit or fonts.com

To generate @font-face files, the Font Squirrel font-face generator should be used.

Always define supporting font-size classes, in conjunction with headers to avoid restyling header sizes.

Reset vs Normalisation

We prefer to use the excellent normalise.css instead of a more heavy-handed reset, like the Meyer reset.

Kickoff, which is used as a base for all of TMW‘s projects, chooses to implement normalisation by default, rather than a CSS reset.

Comments

Comment as much as you can as often as you can. Where it might be useful, include a commented out piece of markup which can help put the current CSS into context.

CSS will be minified before it hits live servers, so don't worry about excessive commenting bloating code - the benefits far outweigh any file-size worries.

Ideally, any CSS ruleset should be preceded by a C-style comment explaining the point of the CSS block. This comment also hosts numbered explanations regarding specific parts of the ruleset. For instance:

/**
 * Helper class to truncate and add ellipsis to a string too long for it to fit
 * on a single line.
 * 1. Prevent content from wrapping, forcing it on a single line.
 * 2. Add ellipsis at the end of the line.
 */
.ellipsis {
  white-space: nowrap; /* 1 */
  text-overflow: ellipsis; /* 2 */
  overflow: hidden;
}

Specificity, IDs and classes

CSS is designed to cascade, so make sure you understand cascading and selector specificity. It will enable you to write very terse and effective code.

Use of IDs and classes effect specificity massively. Only use IDs where deemed necessary, especially on larger builds. Classes are much more modular and portable. If you want to use an ID solely as a JavaScript hook, consider using the ID alongside a class for CSS styling.

Name classes and IDs by the nature of what it is rather than what it looks like. A class of blueBox-left may seem relevant at the time, but should its colour or position change, it will become meaningless. Naming in conjunction with a more OOCSS approach should eliminate this ambiguity.

Read: Shoot to kill – CSS Selector Intent

Conditional Styles

TODO: Modernizr, IE conditional classes

If IE specific styling is required, look to utilise Paul Irish's body/html class conditional for IE* targeting.

!important

It is okay to use !important on helper classes only. To add !important pre-emptively is fine, e.g. .error { color:red!important }, as you know you will always want this rule to take precedence.

Using !important reactively, e.g. to get yourself out of nasty specificity situations, is not advised. Rework your CSS and try to combat these issues by refactoring your selectors. Keeping selectors short and avoiding IDs will help you out here massively.

Magic numbers and absolutes

A magic number is a number which is used because ‘it just works’. These are bad because they rarely work for any real reason and are not usually very futureproof or flexible/forgiving. They tend to fix symptoms and not problems.

For example, using .dropdown-nav li:hover ul { top: 37px; } to move a dropdown to the bottom of the nav on hover is bad, as 37px is a magic number. 37px only works here because in this particular scenario the .dropdown-nav happens to be 37px tall.

Instead you should use .dropdown-nav li:hover ul { top: 100%; } which means no matter how tall the .dropdown-nav gets, the dropdown will always sit 100% from the top.

Every time you hard code a number think twice; if you can avoid it by using keywords or ‘aliases’ (i.e. top: 100% to mean ‘all the way from the top’) or—even better—no measurements at all then you probably should.

Every hard-coded measurement you set is a commitment you might not necessarily want to keep.

Images

Image names should use dashes and be named so that their use is clear i.e. icon-facebook-blue.png

It is hard to advise on a one size fits all solution for images currently. Instead there are a number of methods that should be considered and chosen from when approaching images in CSS.

At TMW, we currently use a combination of GruntIcon, and data-uris, but this is reviewed on a project-by-project basis.

Debugging

If you run into a CSS problem, take code away before you start adding more in a bid to fix it. The problem will exist in the CSS that is already written, more CSS isn't necessarily the right answer!

It can be tempting to put overflow:hidden; on something to hide the effects of a layout quirk, but overflow was probably never the problem; fix the problem, not its symptoms.

Preprocessors

Our CSS preprocessor of choice is Sass. Kickoff, the TMW base framework, contains Sass-based framework that should be used.

Be sure to know the ins-and-outs of excellent vanilla CSS and where a preprocessor can aid that, not hinder or undo it. Learn the downsides of preprocessors inside-out and then fuse the best aspects of the two with the bad bits of neither.

For more specific guidelines on using Sass, read the Sass section of these guidelines.

Responsive Design

All work is considered in a responsive, mobile first, way unless the project dictates otherwise.

Responsive design isn't just a way of developing, it is a way of thinking that needs to flow through the entire site development process from content, UX, design and development.

It is recommended to read Ethan Marcotte's excellent book, Responsive Web Design as well as the related A List Apart article on the subject.


Sass

  • Be sure to know the ins-and-outs of excellent vanilla CSS and where a preprocessor can aid that, not hinder or undo it. Learn the downsides of preprocessors inside-out and then fuse the best aspects of the two with the bad bits of neither.
  • One of the most powerful features of using a preprocessor is simply being able to separate your CSS into a number of different files that are pulled in and combined at compile time. This makes it easier to make your code more maintainable and better structured than having all of your code in one file.

Nesting

When nesting selectors, try not to nest more than 3 levels deep. If you find yourself writing deeply nested selectors, it is usually a sign that you should rethink how you have structured your markup or class declarations.

  • Nest only when it would actually be necessary in vanilla CSS, e.g.
  • Aim for a maximum depth of 3 levels in Sass, this is an absolute maximum
  • The following would be wholly unnecessary in normal CSS
/* Bad */
.header {}
.header .site-nav {}
.header .site-nav li {}
.header .site-nav li a {}

Which produces:

// Bad
.header {
  .site-nav {
    li {
      a {}
    }
  }
}

This Sass is an improved version of the above example:

// Good, or at least better
.header {}
.site-nav {
  li {
    a {}
 }
}

It produces the CSS below:

/* Good */
.header {}
.site-nav {}
.site-nav li {}
.site-nav li a {}

Ideally, one would improve the above code by add more descriptive classes to each item, for example:

// Good
.header {}
.header-nav {}
.header-nav-item {}
.header-nav-item-link {}

@extend

Use caution when using the @extend operator. It can give unexpected results in the compiled CSS when used too liberally and can usually be avoided by using classes to extend styling in a more modular fashion.

For example, rather than writing the following:

.section-centered { display: block; margin: 0 auto; }

.masthead { @extends .section-centered; }

Simply add the class to the masthead markup instead:

<div class="masthead section-centered"></div>

Doing this will help to keep your styling modular and more reusable.

There are of course times that using @extend can be very useful, but don't use it as a defacto when sensible use of vanilla CSS would be just as simple.

Module naming helper mixins

In Kickoff v7, we included some sass mixins to help keep consistent CSS output. Again, these are not required but might prove useful, see below:

@include component('foo') {
  margin: auto;

  @include modifier('bar') {
    padding: 20px;
  }

  @include modifier('baz') {
    padding: 50px;

    @include media(>mid) {
      padding: 20px;
    }
  }

  @include state('active') {
    background-color: green;
  }
}

Which compiles to:

.foo {
  margin: auto;
}

.foo--bar {
  padding: 20px;
}

.foo--baz {
  padding: 50px;
}

@media screen and (min-width: 46.875em) {
  .foo--baz {
    padding: 20px;
  }
}

.foo.is-active {
  background-color: green;
}

Clone this wiki locally