CSS πŸ’…

Hero image for CSS πŸ’…

Formatting

  • Use soft-tabs with a two space indent.
  • Use the SCSS syntax. So not SASS.
  • Use one space between selector and {.
  • Use one space between property and value.
// Bad
width:20px;

// Good
width: 20px;
  • Don’t add a unit specification after 0 values, unless required by a mixin.
  • Use a leading zero in decimal numbers.
// Bad
opacity: .5;

// Good
opacity: 0.5;
  • Avoid using shorthand properties for only one value.
// Bad
background: #f00;

// Good
background-color: #f00;
  • Use space around the operator.
// Bad
$variable*1.5

// Good
$variable * 1.5
  • Use parentheses around individual operations in shorthand declarations.
// Bad
padding: $variable * 1.5 $variable * 2;

// Good
padding: ($variable * 1.5) ($variable * 2);
  • Use double colons for pseudo-elements.
// Bad
element:after {}

// Good
element::after {}
  • Use a blank line above a selector that has styles.
// Bad
.block {
  ...
}
.block__element1 {
  ...
}

// Good
.block {
  ...
}

.block__element1 {
  ...
}
  • Prefer lowercase hex color codes. For readability, pick the shorter version (3 digits) of hex color codes whenever possible.
// Bad
color: white;

// Good
color: #FFF;
color: #1968C7;

// Better
color: #fff;
color: #1968c7;
  • Avoid nesting code more than 3 levels deep as 1) it improves readability since it forces to break large pieces of code into smaller units and 2) it diminishes the risks to create over-qualified selectors when it’s not necessary.
// Bad
.block {

  &__element {
     h1 {
        ...
        span {
        ...
        }
     }
  }
}


// Good
.block {

  &__element {
     h1 {
       ...
     }
     // or h1 span if it's necessary
     span {
        ...
     }
  }
}
  • Prefer breaking down large pieces of CSS code into smaller units. It’s not because we can nest code that all code for a block or element needs to be nested into a giant block. There is no rule that works in 100% of cases but 1) use the HTML structure for the ordering of selectors and 2) use common sense to group blocks and elements as meaningful units.
// Bad
.block {
  ...
  
  &__element1 {
    ...
  }
  
  &__element2 {
    ...
  }
  
  [...]
  
  &__element15 {
    ...
  }
}

// Good
.block {
  ...
}

.block__element1 {
   ...
}

.block__element2 {
  ...
}
  • Use SCSS comments blocks with two forward slashes //. As a general rule, place comments above the line it concerns.
// Bad
/* this is a comment */

// Good
// this is a comment

Naming

Files

  • Use kebab case for file names:
# Bad
media_lightbox.css
mediaLightbox.css

# Good
media-lightbox.css
media-lightbox.scss
  • Add a prefix underscore _ to files which are imported using @import. Manifest files or stylesheets imported directly into a web page (via a link meta tag) must not be prefixed by an underscore.
components/
β”œβ”€β”€ _media-lightbox.scss
application.scss
// In the manifest file: application.scss
@import './components/_media-lightbox';

Class Names

We currently follow the BEM naming conventions:

.block-name__element-name--modifier-name {
  ...
}
  • Class names are written in kebab case.
  • Keep all class names to singular to avoid having some in plural while other in singular. This reduces cognitive fatigue and using a wrong selector.
// Bad
.list-projects {}
.form-orders {}

// Good
.list-project {}
.form-order {}
  • The block name defines the namespace for its elements and modifiers.
  • The element name is separated from the block name by a double underscore __.
  • The modifier name is separated from the block or element name by a double hyphen --.
  • When nesting, an element is always part of a block, not another element. This means that element names can’t define a hierarchy such as block__elem1__elem2.
# Bad
<form class="search-form">
    <div class="search-form__content">
        <input class="search-form__content__input">

        <button class="search-form__content__button">Search</button>
    </div>
</form>

# Good
<form class="search-form">
    <div class="search-form__content">
        <input class="search-form__input">

        <button class="search-form__button">Search</button>
    </div>
</form>

We used to follow SMACSS which also has the concept of state/variations similarly to the modifier of BEM. But to prevent naming fatigue, we opted with a clearer system like BEM.
Be consistent about naming conventions for classes. For instance, if a project is using BEM, continue using it, and if it’s not, do not introduce it.

SCSS

Use kebab case when naming mixins, functions & variables.

// Bad
$color_blue: blue;
@mixin spanColumns() {}

// Good
$color-blue: blue;
@mixin span-columns() {}.

Stylesheets Folder Structure

Our architecture is heavily inspired by SMACSS with some variations based on our experience and usage in projects.

base/
β”œβ”€β”€ _buttons.scss
β”œβ”€β”€ _fonts.scss
β”œβ”€β”€ _forms.scss
β”œβ”€β”€ _layout.scss
β”œβ”€β”€ _list.scss
β”œβ”€β”€ _media.scss
β”œβ”€β”€ _table.scss
β”œβ”€β”€ _typography.scss
components/
β”œβ”€β”€ _app-navigation.scss
β”œβ”€β”€ _button-hamburger.scss
β”œβ”€β”€ _logo.scss
functions/
β”œβ”€β”€ _asset-url.scss
β”œβ”€β”€ _image-url.scss
layouts/
β”œβ”€β”€ default.scss
β”œβ”€β”€ authentication.scss
β”œβ”€β”€ error.scss
mixins/
β”œβ”€β”€ _text-truncate.scss
screens/
β”œβ”€β”€ home.scss
β”œβ”€β”€ login.scss
theme/
β”œβ”€β”€ _filestack.scss
β”œβ”€β”€ _pygments.scss
  • base/: The overrides of built-in HTML tags and selectors. These are usually normalization styles or an addendum to an external normalization stylesheet such as Normalize.

  • components/: The custom components created for the application. 90% of all stylesheets code is generally located in this folder.

  • functions/: SCSS functions.

  • layouts/: The distinct and shared page layout styles.

  • mixins/: SCSS mixins.

  • screens/: The overrides or custom styles (not shared) required on a screen basis.

  • vendor/: Overrides of third party modules styles. Since these components were not created by us, we simply override the styles.

  • ./: The root of the styles folder contains shared config e.g. _variables.scss, generated files e.g. _icon-sprite.scss and the CSS manifest file e.g. application.scss

The CSS files needs to be imported in this order in the manifest file:

// Import dependencies
@import '~/node_modules/normalize/';

@import 'variables';
// Other generated files
@import 'icon-sprite.scss';

@import 'functions/*';
@import 'mixins/*';

@import 'base/*';
@import 'layouts/*';
@import 'components/*';
@import 'screens/*';
@import 'vendor/*';

The parent folder must be named stylesheets/ following the Ruby on Rails convention. So styles/ or css/ are not valid.

Base

The following files are usually required:

  • _buttons.scss: Default styling for <button> and <input> of types button, reset and submit. These selectors are placed outside of base/_form.scss as <button> tags can be placed of forms.
  • _fonts.scss: Definition of custom @font-face.
  • _forms.scss: Default styling for <form>, <input> (except of buttons, see above), <select>, <textarea>, <legend>.
  • _layout.scss: Default styling for <html> and <body>.
  • _list.scss: Default styling for <ul>, <ol> and <dl>.
  • _media.scss: Default styling for <img>, <figure> and <i> (when used for icons).
  • _table.scss: Default styling for <table> and related tags e.g <td>, <th>…
  • _typography.scss: Default styling for text content e.g. <h1>, <a> or <p>.

Avoid targeting selectors using class names in base/ but instead the raw tags.

Components

  • Each file must contain only one component.
  • Each component is namespaced by a BEM block name. The file name must match the block name in snake case.
// File is named "button-hamburger.scss"
.button-hamburger {
  ...
}
  • Each styles block must be prefixed by the block element name.
// Bad
.button-hamburger {
  ...
}

.text-fallback {
  ...
}

// Good
.button-hamburger {
  ...
}

.button-hamburger .button-hamburger__text-fallback {
  ...
}

Functions

  • Each file must contain only one function.
  • The file name must match the function name in snake case.
  • Prepend the function code by a doc block with input and output details.
// File is named "asset-url.scss"

// Generate urls for an asset file
//
// @param {String} $file - The path to the file
// @return {String} - The url property value
@function asset-url($file_path) {
  ...
}

Layouts

  • A layout is the shared page structure of a page/screen. Think that there are some pages with a single column layout while others with a two-column layout (a sidebar and a main area). In this case, there would be two layout files.
  • At the minimum it must contain one application-wide layout called default.scss. The application layout is the one that is the most widely used in the application.
  • The layout class name must be placed on the top most tag which is <html>.
  • Target only top level selectors (close to <body>) that are used to create the layout.
<html class="layout-default">
    <body>
        <aside class="app-sidebar">
            <nav class="app-nav">
            ...
            </nav>
        </aside>
        <main class="app-content">
            <ul class="list-project"></ul>
            ...
        </main>
    </body>
</html>

Then the layout file must only contain these selectors:

.layout-default {

   .app-sidebar {
    ...
    // No styles for .app-nav here as it does not impact the layout
   }
   
   .app-content {
    ...
    // No styles for .list-project as it does not impact the layout
   }
}

Creating the layout styles must be done first. Therefore, it’s important to dedicate enough time identifying all the possible layouts in an application before moving to creating components or screens.

Mixins

  • Each file must contain only one mixin.
  • The file name must match the mixin name in snake case.
// File is named "text-truncate.scss"

@mixin text-truncate() {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  -o-text-overflow: ellipsis;
  -ms-text-overflow: ellipsis;
}

Screens

  • Each file should target only one screen. In some rare cases, it could target a group of screens e.g. authentication.scss would target login, registration and forget-password. But it would still be preferable to have three files login.scss, registration.scss and forget-password.scss.
  • The file name must match the screen name in snake case.
  • The screen class name is usually placed on the second top most tag which is <body>.
<html class="layout-default">
    <body class="home">
        <aside class="app-sidebar">
            <nav class="app-nav">
            ...
            </nav>
        </aside>
        <main class="app-content">
            <ul class="list-project"></ul>
            ...
        </main>
    </body>
</html>
  • Each styles block must be prefixed by the screen block name.
.home {

    .app-sidebar {
      ...
    }
}

Vendor

  • This folder can be omitted if the project does not require overriding third party modules styles.
  • The file name must match the module name in snake case.
  • Make sure that the selectors do not conflict with the application code.

Responsive Stylesheets

We follow a mobile first approach in all projects. This assumes that all code first work on small/mobile screens then we override in order the ascending order of screen width breakpoints:

.block {
  // shared code for all screens sizes or specific to small/mobile screens
  
  @media only screen and (min-width: 768px) { 
    // Overrides or specific styles for table/phablet screens
  }
  
  @media only screen and (min-width: 1140px) { 
    // Overrides or specific styles for desktop screens
  }
}
  • Do not wrap several selectors in a media query block, instead place each media query into each selector. It allows to group all styles for a selector across all screen sizes which is easier to manage.
// Bad
.block1 {
  ...
}

.block2 {
  ...
}

@media only screen and (min-width: 768px) { 
  .block1 {
    ...
  }
  
  .block2 {
    ...
  }
}


// Good
.block1 {
  ...
  @media only screen and (min-width: 768px) { 
    ...
  }
}

.block2 {
  ...
  @media only screen and (min-width: 768px) { 
    ...
  }
}
  • Do not hard code breakpoints values but instead store them in shared variables. An even better solution is to use a mixin to generate the media query block.
// Bad
.block1 {
  ...
  @media only screen and (min-width: 768px) { 
    ...
  }
}

.block2 {
  ...
  @media only screen and (min-width: 768px) { 
    ...
  }
}

// Good
// In _variables.scss
$grid: (
  tablet: 768px,
  desktop: 1140px
);

// In other files
.block1 {
  ...
  @media only screen and (min-width: map-get($grid, 'tablet')) { 
    ...
  }
}

.block2 {
  ...
  @media only screen and (min-width: map-get($grid, 'tablet')) { 
    ...
  }
}

// Better
// In mixins/_media-breakpoint.scss
@mixin media-breakpoint($size) {
    @media only screen and (min-width: $size) { 
        @content;
    }
}

// In other files
.block1 {
  ...
  @include media-breakpoint(map-get($grid, 'tablet')) { 
    ...
  }
}

.block2 {
  ...
  @include media-breakpoint(map-get($grid, 'tablet')) { 
    ...
  }
}