26 Rules I Follow When Writing CSS To Make It Concise and Readable

Arbaoui Mehdi
12 min readSep 18, 2022
CSS Stylelint Errors Examples
CSS Stylelint Errors Examples

Not all developers follow the same rules when writing CSS. This is especially true if you work with a team of front-end developers. You’ll likely see code that has been written in an incoherent/unreadable form which can affect both frontend/backend developers comprehension.

The benefits of writing code in a consistent way are numerous, from the perspective of your team and company to your project itself. Using a linter to verify and enforce those rules will ensure your team is writing standards-compliant code and prevent bugs from being introduced.

“So if you want to go fast, if you want to get done quickly, if you want your code to be easy to write, make it easy to read.” — Robert C. Martin

In this article, we’ll go over my coding CSS Guidelines listed in a .stylelintr file that contains a list of good practices for writing CSS to extend its readability and maintainability even when the codebase grows;

But first, let’s check what can be done following these rules.

Rules Summary

1 — Don’t use IDs for styling.

2 — Don’t Reference or style descendent elements in your class selectors

3 — Remove all trailing white space for your files

4 — Don’t mix spaces with tabs for indentation

5 — Separate your code into a logical section using standard comment blocks.

6 — Leave one clear line under your section comments.

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

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

9 — Include a single space after the colon of a declaration.

10 — Include a semi-colon at the end of every declaration in a declaration block

11 — Include a space after each comma in comma-separated property or function values.

12 — CSS Blocks should be separated by a single clear line

13 — Always use double quotes, quote attribute values in selectors

14 — Use lower-case and shorthand hex values

15 — Where allowed, avoid specifying units of zero values

16 — Disallow redundant values in shorthand properties.

17 — Never use !important

18 — Set properties explicitly

19 — Enforces sass variables in any CSS property using hex colors, except for the currentColor and inherit values.

20 — Sorts related property declarations by grouping together following the order (Sass Inheritance, Positioning, Box Model, Typography, Visual, Animation, Misc)

21 — Declare pseudo-classes with a single colon

22 — Declare pseudo-elements with a double colon

23 — 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.

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

25 — Aim for maximum depth of just 1 nested rule

26 — 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>]

Use Classes for styling elements

When you use IDs to style, you are tying that element to a DOM node, and giving every node of this type the same styling. This implies if you ever choose to move the element, your CSS will need updating. Plus If you’re nesting elements with IDs, you run the risk of IDs conflicting with each other on different levels of the nested elements.

IDs are ultimately just indicators of element identity. They stand out as the given element to be manipulated. But using an ID in CSS has no real meaning, in almost all cases, you should use classes with a combination of pure CSS selectors and child combinators to maintain high specificity.

Stylelint rule

"selector-max-id": 0

Examples

Do use classes for styling.

.component {
...
}

❌ Don’t use IDs for styling.

#component {
...
}

✅ Do Style the base elements (such as typography elements)

h1 {
...
}

❌ Don’t Reference or style descendent elements in your class selectors

.component h1 {
...
}

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.

Try to be specific enough so that no more than one element matches the selector. Be sure to add specificity to where it matters, while keeping it low at all other times. This will ensure a lean and fast-loading.

So instead of focusing on the intricacies of the selectors, focus on the content in the markup. Better yet, try not to be overly specific and use a class if it makes sense to do so.

Examples

✅ Do

.user-list-link:hover {
...
}

❌ Don’t

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

Formatting & Indentation

Remove all trailing white space from your CSS code. This will ensure that your file is clean and easily readable for future reference.

Stylelint rule

"no-eol-whitespace": true

Unless you have a unique reason for using spaces instead of tabs, there really isn’t a reason to mix tabs and spaces. Each indentation depth is worth approximately two spaces.

But if there was one takeaway to remember it would be this: developers won’t care which experimental hipster indentation rules you use as long as they are applied consistently across your document… which means stick to:

“Don’t mix spaces with tabs for indentation.”

Stylelint rule

"indentation": 2

Commenting

Using a good Classe naming convention like BEM already gives a context of to your CSS code, still, I like to put a comment at the top of my stylesheet that describes what the entire file is doing, for that:

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

I recommend having a clear purpose for each comment block and not writing a wall of text. The intent is to make the code more readable, so the comments should be short and sweet and not longer definitions.

Example

// ===================================================================
// 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
}

Spacing

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

Stylelint rule

"selector-list-comma-newline-after": "always"

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

Stylelint rule

"block-closing-brace-space-before": "always"
  • 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

CSS quotes are vital when it comes to creating selectors. They are utilized to enclose string values within the stylesheet, and the quotes must be double.

The reason for this is because CSS supports escaping characters (e.g., *, &, #), and if you only use single quotes in your stylesheet, then you won’t be able to escape those characters, so stick to using double quotes to quote attribute values in selectors

Examples

✅ Do

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

❌ Don’t

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

Declaring Values

The goal of the CSS unit system is to provide a way to declare values that are easy to read and understand. Unit values should be chosen so that they are clear and concise.

When declaring CSS values, use lower-case, hexadecimal, and shorthand unit values. Use the shortest possible unit value for each property you declare, the rules below can be a good fit for writing values with readability in mind:

  • Use lower-case and shorthand hex values
  • Use unit-less line-height values
  • Where allowed, avoid specifying units of zero values
  • Set properties explicitly (avoid !important).
  • background-color: #333 over background: #333
  • margin-top: 10px overmargin: 0px 0 0

Examples

✅ Do

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

❌ Don’t

.component {
background: #ccc;
color: #aaaaaa;
left: 0px;
line-height: 24px;
height: 400px !important;
padding: 0px 20px 0px 20px;
top: 0px;
}

Declarations order

The order of CSS declarations is essential. The most typical problem I witness in some code bases is when the order of declarations is not consistent. This can make the code challenging to read, and it can also induce unexpected results in browsers.

When you write CSS, it’s often helpful to order declarations to make them easier to read following the same writing pattern.

The example bellow follows an order of:

  • Declarations
  • Sass Inheritance in case of using Sass
  • Content
  • Positioning
  • Box Model
  • Typography
  • Visual
  • Animation
  • Misc
.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;
}

Examples

✅ 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;
}

❌ 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;
}

Pseudo Elements and Classes

Pseudo-elements are a way of adding synthetic content to HTML elements. They are created with a single colon : after the element name and can be used to create additional styling for that element.

✅ Declare pseudo classes with a single colon

.component:focus {
...
}

.component:hover {
...
}

✅ Declare pseudo elements with a double colon

.component::before {
...
}

.component::after {
...
}

❌ Don’t

.component:after {
...
}

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.

The examples below will show some Sass code.

✅ 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;
}
}

At its worst, this produces:

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

❌ 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;
}
}
}
}
}
}

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;
}

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 architectural 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 {
/*...*/
}
<article class="myComponent">...</article>

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 {
...
}
<button class="button button--primary">...</button>

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>

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>
<ul class="breadcrumbs">
<li class="breadcrumb">
<a class="breadcrumb-label" href="#"></a>
</li>
</ul>

❌ Avoid verbose descendant names

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

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>.tweet {
...
}
.tweet.is-expanded {
...
}

Conclusion

Just looking at your code after a while can be confusing, after all, and as such, it’s easy to accidentally introduce bugs when writing new code, or simply forget to add a particular style of punctuation. A linter will scan each individual file that you’ve written and display any errors on the fly — before your code is even checked into your project’s main branch.

In this post, we’ve reviewed some of the most useful linting rules that I’m applying personally for writing code in a consistent and readable way.
Using a CSS linter and style guide saves you hours of time having to debug code and untangle naming conflicts and other problems.

Thanks for Reading!

If you found this article helpful, you can show your appreciation by clicking on the clap button and leaving a comment below!

--

--