DEV Community

loading...
Cover image for How I'm forcing myself to write CSS following certain rules

How I'm forcing myself to write CSS following certain rules

arbaoui_mehdi profile image Arbaoui Mehdi 🇲🇦 ・11 min read

Forcing yourself to write a CSS code that follows a guideline can help the person ensue a list of good practices to write a CSS code reflecting a definite logic. Plus, it can adapt the whole team to a coding guideline to avoid incoherence across the entire code base.

I wrote the .stylelintrc list of practices to adapt to how I embrace the CSS language; moreover, it can be tweakable depending on the context, and this is a summary of what can be done following these rules.

Check the Coding Guidelines that cover a thorough description of all the .stylelintrc rules

Sass Codings Rules Summary

  • Don't use IDs for styling.
  • Don't Reference or style descendent elements in your class selectors
  • Remove all trailing white space for your files
  • Don't mix spaces with tabs for indentation
  • Separate your code into a logical section using standard comment blocks.
  • Leave one clear line under your section comments.
  • CSS rules should be comma-separated but live on new lines
  • Include a single space before the opening brace of a rule-set
  • Include a single space after the colon of a declaration.
  • Include a semi-colon at the end of every declaration in a declaration block
  • Include a space after each comma in comma-separated property or function values.
  • CSS Blocks should be separated by a single clear line
  • Always use double quotes, quote attribute values in selectors
  • Use lower-case and shorthand hex values
  • Where allowed, avoid specifying units of zero values
  • Disallow redundant values in shorthand properties.
  • Never use !important
  • Set properties explicitly
  • Enforces sass variables in any CSS property using hex colors, except for the currentColor and inherit values.
  • Sorts related property declarations by grouping together following the order (Sass Inheritance, Positioning, Box Model, Typography, Visual, Animation, Misc)
  • Declare pseudo-classes with a single colon
  • Declare pseudo-elements with a double colon
  • Use rem units as primary unit type, This includes:
  • Positioning: top, right, bottom, left
  • Dimensions: width, height, margin, padding
  • Font Size --> Use px units as a primary unit type for the following properties
  • Border widths: border: 1px solid #bada55;
  • Line-height should be kept unitless. If you find you are using a line-height with a set unit type, try to think of alternative ways to achieve the same outcome. If it's a unique case that requires a specific px or rem unit, outline the reasoning with comments so that others are aware of its purpose.
  • Avoid all use of magic numbers. Rethink the problem (margin-top: 37px)
  • Aim for maximum depth of just 1 nested rule
  • Components Syntax should follow <componentName>[--modifierName|-descendantName]
  • .componentName must be written in camel case.
  • .componentName class names as short as possible but as long as necessary.
  • --modifierName must be written in camel case
  • --modifierName must be separated from the component name by two hyphens
  • descendantName must be written in camel case
  • Use componentName.is-stateOfComponent for state-based modifications of components
  • is-stateOfComponent must be camel case
  • is-stateOfComponent should be used as an adjoining class
  • Variables should be name such [<componentName>[--modifierName][-descendantName]-]<propertyName>-<variableName>[--<modifierName>]

Coding Guidelines

General Principles

✅ Use Classes in SCSS for styling elements.

Stylelint Rule

"selector-max-id": 0
Enter fullscreen mode Exit fullscreen mode

Example

.component {
  ...
}
Enter fullscreen mode Exit fullscreen mode

❌ Don't use IDs for styling.

#component {
  ...
}
Enter fullscreen mode Exit fullscreen mode

✅ Style the base elements (such as typography elements)

h1 {
  ...
}
Enter fullscreen mode Exit fullscreen mode

❌ Reference or style descendent elements in your class selectors

.component h1 {
  ...
}
Enter fullscreen mode Exit fullscreen mode

Performance

Overly specific selectors can cause performance issues, it requires a lot of DOM walking and for large documents can cause a significant increase in the layout time.

❌ Don't

ul.user-list li span a:hover {
  ...
}
Enter fullscreen mode Exit fullscreen mode

✅ Do

.user-list-link:hover {
  ...
}
Enter fullscreen mode Exit fullscreen mode

Formating & Indentation

Remove all trailing white space for your files

"no-eol-whitespace": true
Enter fullscreen mode Exit fullscreen mode

Don't mix spaces with tabs for indentation

"indentation": 2
Enter fullscreen mode Exit fullscreen mode

Commenting

  • Separate your code into a logical section using standard comment blocks.
  • Leave one clear line under your section comments.

✅ Do

// =============================================================================
// FILE TITLE / SECTION TITLE
// =============================================================================


// Comment Block / Sub-section
// -----------------------------------------------------------------------------
//
// Purpose: This will describe when this component should be used. This comment
// block is 80 chars long
//
// 1. Mark lines of code with numbers which are explained here.
// This keeps your code clean, while also allowing detailed comments.
//
// -----------------------------------------------------------------------------

.component {
    ... // 1
}
Enter fullscreen mode Exit fullscreen mode

Spacing

CSS rules should be comma-separated but live on new lines

"selector-list-comma-newline-after": "always"
Enter fullscreen mode Exit fullscreen mode

Include a single space before the opening brace of a rule-set

"block-closing-brace-space-before": "always"
Enter fullscreen mode Exit fullscreen mode
  • Include a single space after the colon of a declaration.
  • include a semi-colon at the end of every declaration in a declaration block
  • include a space after each comma in comma-separated property or function values.
  • CSS Blocks should be separated by a single clear line

Quotes

✅ Always use double quotes, quote attribute values in selectors

input[type="checkbox"] {
  background-image: url("/img/you.jpg");
  font-family: "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial;
}
Enter fullscreen mode Exit fullscreen mode

❌ Don't

input[type="checkbox"] {
  background-image: url(/img/you.jpg);
  font-family: Helvetica Neue Light, Helvetica Neue, Helvetica, Arial;
}
Enter fullscreen mode Exit fullscreen mode

When Declaring Values

  • Use lower-case and shorthand hex values
  • use unit-less line-height values
  • Where allowed, avoid specifying units of zero values
  • Never use !important
  • Set properties explicitly

    • background-color: #333 over background: #333
    • margin-top: 10px over margin: 10px 0 0

✅ Do

.component {
  background-color: #ccc;
  color: #aaa;
  left: 0;
  line-height: 1.25;
  min-height: 400px;
  padding: 0 20px;
  top: 0;
}
Enter fullscreen mode Exit fullscreen mode

❌ Don't

.component {
  background: #ccc;
  color: #aaaaaa;
  left: 0px;
  line-height: 24px;
  height: 400px !important; //jerk #yolo FUUUUUU
  padding: 0px 20px 0px 20px;
  top: 0px;
}
Enter fullscreen mode Exit fullscreen mode

Declaration order

The goal is to understand the essence of the styles by reading few declarations. Most of the time, the essence is how the element is laid out and its size is determined. A proper ordering of rules allows to scan the declaration block for properties quickly, and this is the order that should be followed:

.declarationOrder {
  /* Declarations */
  $varName: #ccc;

  /* Sass Inheritance */
  @extend %a-placeholder;
  @include silly-links;

  // Content
  content: "";

  // Positioning
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 10;

  // Box Model
  display: block;
  float: right;
  width: 100rem;
  height: 100rem;
  padding: 10rem;
  margin: 10rem;

  // Typography
  color: $varName;
  font: normal 1rem "Helvetica", sans-serif;
  line-height: 1.3;
  text-align: center;

  // Visual
  background-color: $varName;
  border-radius: 4px;
  opacity: 1;

  // Animation
  transition: all 1s;

  // Misc
  user-select: none;
}
Enter fullscreen mode Exit fullscreen mode

✅ Do

.component {
  // Sass Inheritance
  @extend %a-placeholder;
  @include silly-links;

  // Position and Layout
  top: 0;
  left: 0;

  // Box Model
  width: 150px;
  min-height: 400px;
  padding: 0 20px;

  // Typography
  line-height: 1.25;
  color: #aaa;
}
Enter fullscreen mode Exit fullscreen mode

❌ Don't

.component {
  top: 0;
  padding: 0 20px;
  @include silly-links;
  left: 0;
  @extend %a-placeholder;
  min-height: 400px;
  line-height: 1.25;
  width: 150px;
  color: #aaa;
}
Enter fullscreen mode Exit fullscreen mode

Pseudo Elements and Classes

Pseudo Elements and classes are very different things, as is the syntax used to declare them:

✅ Declare pseudo classes with a single colon

.component:focus {
    ...
}

.component:hover {
    ...
}
Enter fullscreen mode Exit fullscreen mode

✅ Declare pseudo elements with a double colon

.component::before {
    ...
}

.component::after {
    ...
}
Enter fullscreen mode Exit fullscreen mode

❌ Don't

.component:after {
    ...
}
Enter fullscreen mode Exit fullscreen mode

Units

✅ Do

  • Use rem units as primary unit type, This includes:
    • Positioning: top, right, bottom, left
    • Dimensions: width, height, margin, padding
    • Font Size
  • Use px units as a primary unit type for the following properties
    • Border widths: border: 1px solid #bada55;
  • Line-height should be kept unit-less. If you find you are using a line height with a set unit type, try to think of alternative ways to achieve the same outcome. If it's a unique case that requires a specific px or rem unit, outline the reasoning with comments so that others are aware of its purpose.

❌ Don't

  • Avoid all use of magic numbers. Rethink the problem (margin-top: 37px)

Nesting

Nesting is handy, sometimes, but will conflict with our Specificity and Performance guidelines.

As we follow conventions and thoughts from widely accepted methodologies such as BEM, the use of the suffix can help immensely though.

  • Just because you can, doesn't mean you should.
  • Watch your output, be mindful of Specificity and Performance described in this document.

✅ Aim for maximum depth of just 1 nested rule

.panel-body {
  position: relative;
}

.panel-sideBar {
  z-index: 10;
}

.panel-sideBar-item {
  cursor: pointer;
}

.panel-sideBar-item-label {
  color: #aeaeae;

  &.has-smallFont {
    font-size: 1rem;
  }
}
Enter fullscreen mode Exit fullscreen mode

At its worst, this produces:

.panel-sideBar-item-label.has-smallFont {
  font-size: 1rem;
}
Enter fullscreen mode Exit fullscreen mode

❌ Don't

.bc-tab-panel {
  .panel-body {
    position: relative;

    .panel-side-bar {
      z-index: 10;

      .panel-side-item {
        cursor: pointer;

        .panel-side-item-label {
          color: #aeaeae;

          &.small-font {
            font-size: 1rem;
          }
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

At its worst, this produces:

.bc-tab-panel
  .panel-body
  .panel-side-bar
  .panel-side-item
  .panel-side-item-label.small-font {
  font-size: 13px;
}
Enter fullscreen mode Exit fullscreen mode

@extends or @include

  • Excessive use of @include can cause unnecessary bloat to your stylesheet, but gzip should help with that
  • Excessive use of @extend can create a large selector block (not helpful in web inspector) and hoisting of your selector can cause override and inheritance issues.

✅ We advise to @include over @extend generally but use common sense. In situations where it's better to @extend it's safer to do so on a placeholder selector

✅ Make use of placeholder selectors to separate repeated local styles

%placeholderSelector {
  background-color: #eee;
}

.component1 {
  @extend %placeholderSelector;
  color: red;
}

.component2 {
  @extend %placeholderSelector;
  color: blue;
}
Enter fullscreen mode Exit fullscreen mode

Components

Syntax: <componentName>[--modifierName|-descendantName]

This component syntax is mainly taken from Suit CSS with minor modifications.

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

❌ Do not choose a class name base on its visual presentation or its content

✅ The Primary achitectural division is between components and utilities:

  • componentName (eg. .dropdown or .buttonGroup)
  • componentName--modifierName (eg. .dropdwon--dropUp or .button--primary)
  • componentName-descendantName (eg. .dropdown-item)
  • componentName.is-stateOfComponent (eg. dropdown.is-active)
  • u-utilityName (eg. u-textTruncate)

ComponentName

The component's name must be written in the camel case. Use class names as short as possible but as long as necessary.

  • Example .nav not .navigation
  • Example .button not .btn
.myComponent {
  /*...*/
}
Enter fullscreen mode Exit fullscreen mode
<article class="myComponent">...</article>
Enter fullscreen mode Exit fullscreen mode

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.

.button {
  ...
}

.button--primary {
  ...
}
Enter fullscreen mode Exit fullscreen mode
<button class="button button--primary">...</button>
Enter fullscreen mode Exit fullscreen mode

componentName-descendantName

A component descendant is a class this 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 the camel case.

<article class="tweet">
  <header class="tweet-header">
    <img class="tweet-avatar" src="{$src}" alt="{$alt}" />
    ...
  </header>

  <div class="tweet-body">...</div>
</article>
Enter fullscreen mode Exit fullscreen mode

You might notice that tweet-avatar, despite being a descendant of tweet-header does not have the class of tweet-header-avatar. WHY? because it doesn't necessarily have to live there. It could be adjacent to tweet-header and function the same way. Therefore, you should only prepend a descendant with its parent if must live there. Strive to keep class names as short as possible, but as long as necessary.

When building a component, you'll often run into the situation where you have a list, group or simply require a container for some descendants. In this case, it's much better to follow a pattern of pluralizing the container and having each descendant be singular. This keeps the relationship clear between descendant levels.

✅ Do

<nav class="pagination">
  <ul class="pagination-list">
    <li class="pagination-listItem">...</li>
  </ul>
</nav>
Enter fullscreen mode Exit fullscreen mode
<ul class="breadcrumbs">
  <li class="breadcrumb">
    <a class="breadcrumb-label" href="#"></a>
  </li>
</ul>
Enter fullscreen mode Exit fullscreen mode

❌ Avoid verbose descendant names

<nav class="pagination">
  <ul class="pagination-pages">
    <li class="pagination-pages-page">...</li>
  </ul>
</nav>
Enter fullscreen mode Exit fullscreen mode
<ul class="breadcrumbs">
  <li class="breadcrumbs-breadcrumb">
    <a class="breadcrumbs-breadcrumb-label" href="#"></a>
  </li>
</ul>
Enter fullscreen mode Exit fullscreen mode

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 or any backend language 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).

<article class="tweet is-expanded">...</article>
Enter fullscreen mode Exit fullscreen mode
.tweet {
    ...
}

.tweet.is-expanded {
    ...
}
Enter fullscreen mode Exit fullscreen mode

Utilities

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.

Utility classes should be used sparingly, lean towards component level styling to make for as reusable HTML patterns as possible.

u-utilityName

Syntax: u-<utilityName>
Utilities must use a camel case name, prefixed with a u namespace.

Variables and Mixins

Variables and Mixins should follow similar naming conventions.

Variables

Syntax: [<componentName>[--modifierName][-descendantName]-]<propertyName>-<variableName>[--<modifierName>]

Variables should be named as such, things that can change over time.

Variables should also follow our component naming convention to show context and be in camelCase. If the variable is a global, generic variable, the property name should be prefixed first, followed by the variant and or modifier name for a clearer understanding of use.

✅ Abstract your variable names

$color-brandPrimary: #aaa;
$fontSize: 1rem;
$fontSize--large: 2rem;
$lineHeight-small: 1.2;
Enter fullscreen mode Exit fullscreen mode

❌ Name your variables after the color value

$zirotoheroBlue: #00abc9;
$color-blue: #00ffee;
$color-lightBlue: #eeff00;
Enter fullscreen mode Exit fullscreen mode

Component / Micro App level variables

Micro apps must base their local variables on the global variables primarily. You may add your own specific variables as required if no global variable is available.

For portability, your component should declare its own set of variables inside its own settings partial, inside the settings folder.

If your variable is scoped to your component, it should be namespaced as such following component naming conventions.

✅ Do

$componentName-fontSize:                                fontSize("small");
$componentName-decendantName-backgroundColor:           #ccc;
$componentName-decendantName-marginBottom:              fontSize("large");
$componentName-decendantName--active-backgroundColor:   #000;
Enter fullscreen mode Exit fullscreen mode
.componentName {
    font-size: $componentName-fontSize;
}

.componentName-decendantName {
    background-color: $componentName-decendantName-backgroundColor;
    margin-bottom: $componentName-decendantName-marginBottom;
}

.componentName-decendantName--active {
    background-color: $componentName-decendantName--active-backgroundColor;
}
Enter fullscreen mode Exit fullscreen mode

Maps

Variable maps with a simple getter mixin, can help simplify your variable names when calling them, and help better group variables together using their relationship.

✅ Do

// Setting variables and mixin
// -----------------------------------------------------------------------------

$colors: (
    primary: (
        base: #00abc9,
        light: #daf1f6,
        dark: #12799a
    ),
    secondary: (
        base: #424d55,
        light: #ccc,
        lightest: #efefef,
        dark: #404247
    ),
    success: (
        base: #bbd33e,
        light: #eaf0c6
    )
);

@function color($color, $tone: "base") {
    @return map-get(map-get($colors, $color), $tone);
}
Enter fullscreen mode Exit fullscreen mode
// Usage
// -----------------------------------------------------------------------------

body {
    color: color("secondary");
}

h1,
h2,
h3 {
    color: color("secondary", "dark");
}

.alert {
    background-color: color("primary", "light");
}

.alert-close {
    color: color("primary");
}

.alert--success {
    background-color: color("success", "light");

    > .alert-close {
        color: color("success");
    }
}
Enter fullscreen mode Exit fullscreen mode

Every variable used in the core architecture must be based on the global
variables.

Colors

Use the globally available colors.
A component shouldn't really have a need for a new color.
This creates consistency and sanity.

Avoid using the darken(color, %) and lighten(color, %) mixins for similar reasons.

Use the color maps available to you:

.component {
    background-color: color("brand", "primary");
}
Enter fullscreen mode Exit fullscreen mode

z-index scale

Use the z-index scale under global settings.

zIndex("lowest") or zIndex("high") for example.

Font Weight

Never declare a new font-weight,
only use the available font settings. e.g.

fontWeight("light");
fontWeight("semibold");
Enter fullscreen mode Exit fullscreen mode

Line Height

We provide a line-height scale. This should be used for blocks of text. e.g.

lineHeight("smallest");
lineHeight("large");
Enter fullscreen mode Exit fullscreen mode

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.

.button {
  height: remCalc(50px);
  line-height: remCalc(49px);
}
Enter fullscreen mode Exit fullscreen mode

Animations

Animation delays, durations, and easing should be taken from the global framework

Mixins

Mixins follow regular camel case naming conventions and do not require namespacing. If you are creating a mixin for a utility, it will need to match the utility name (including u namespacing).

  • @mixin buttonVariant;
  • @mixin u-textTruncate;

Discussion (2)

pic
Editor guide
Collapse
alohci profile image
Nicholas Stimpson • Edited

You're the guy in the hair shirt, right? You're throwing away much of the power and beauty of the cascade there.

Collapse
devdof profile image
Hatim Lagzar

Never thought about something like this.
I can forsee the power of this guidelines.

Thank you so much