Web front-end
15 minute read

Clean Code: Why HTML/CSS Essentials Still Matter

Tigran is an experienced front-end developer with a passion for clean and performant CSS code. He likes to keep it simple and fast.

During my 15 years of experience in web development, I’ve worked with various companies, from enterprise-level to startups, and I’ve worked with many, many fellow software engineers along the way. One of the most common and important problems I’ve came across in my projects has been failure to write clean and easy-to-understand code.

Even the best developers from top-notch companies don’t always follow best practices and write code which can be cleaned and optimized.

The Consequences of Messy and Dirty Code

To quote an old but still relevant blog post discussing clean code principles:

Source code is akin to financial debt. Suppose you want to buy a house to live in. Most people don’t have the financial wherewithal to pay cash for a house, so you take out a mortgage instead. But the mortgage itself is not a great thing to have. It’s a liability. You have to pay interest on your debt every month…

So too in web development. The code has ongoing costs. You have to understand it, you have to maintain it, you have to adapt it to new goals over time. The more code you have, the larger those ongoing costs will be. It’s in our best interest to have as little source code as possible while still being able to accomplish our business goals.

Abstract image of clean code design

But let’s not dwell on the abstract. This article will cover essential clean code principles, different techniques used to organize code, and how to make it more maintainable, scalable, and easier to debug.

Quality code starts with basic code styling, expanding to best practices when writing large apps in HTML/CSS with many reusable blocks, and we will also discuss naming conventions to create greater implicit readability as well as third-party frameworks and their best practices.

By the time you’re done reading this, you should have a good understanding of the basics of quality code and how to make your HTML and CSS code easier to maintain and extend.

Code Styling Essentials

Let’s start with the basics of good HTML and CSS code: W3C validity, naming conventions, file structure, and so on.

Be mindful of the structure from day one.

If you are going to develop a big app, then you need to take care of the file structure. It can, or rather should, look like this:

Image of the file structure for a big app

Use validators to check your code.

Try to always use HTML and CSS validators.

Bad code:

<figure>
  <div>
    <img src="demo.jpg" alt="">
    <figcaption>
      <h2>Hello world</h2>
    </figcaption>
  </div>
</figure>

<picture>
  <source src="demo.webp" type="image/webp">
  <img src="demo.jpg" alt="">
</picture>

<details>
  <header>
    <summary>Expand details</summary>
  </header>
  <p>All content goes here</p>
</details>
p {
  font: 400 inherit/1.125 serif;
}

Good code:

<figure>
  <img src="demo.jpg" alt="">
  <!-- figcaption should be child of element figure element, not div -->
  <figcaption>
    <h2>Hello world</h2>
  </figcaption>
</figure>

<picture>
  <!-- source should have srcset attribute -->
  <source srcset="demo.webp" type="image/webp">
  <img src="demo.jpg" alt="">
</picture>

<details>
  <!-- summary is not allowed as child of header -->
  <summary>Expand details</summary>
  <p>All content goes here</p>
</details>
p {
  font-weight: 400;
  font-size: inherit;
  line-height: 1.125;
  font-family: serif;
}

Use alternative text on <img> tags for clean code principles validity.

This attribute plays vital role for SEO, search engines, web crawlers, screen readers, etc.

Bad code:

<img src="demo.jpg">

Good code:

<img src="demo.jpg" alt="This is placeholder of the image">

Use kebab-case (spinal-case).

For names, try to use kebab-case (spinal-case) and not use camelCase, or under_score. Use under_score only when using BEM, though if you are using Bootstrap, it’s better to keep consistent and go with - as a delimiter.

Bad code:

<section class="section_featured_Listing">
  <h1 class="largeTitle">H1 title</h1>
</section>

Good code:

<section class="section-featured-listing">
  <h1 class="large-title">H1 title</h1>
</section>

kebab-case is more readable then camelCase and under_score.

Use meaningful names that can be understood by anyone, keep them short.

The names of classes should look like .noun-adjective.

Try to use common names of classes instead of writing content specific names. It makes code more readable

Bad code:

<div class="team-wrapper">
  <button class="large-button-green"></button>
  <p class="greenText-john-smith-description"></p>
  <div class="white-bg"></div>
</div>

Good code:

<div class="section-team">
  <button class="btn-lg btn-success"></button>
  <p class="text-success"></p>
  <div class="bg-white"></div>
</div>

Don’t write type attributes for stylesheets and script in HTML5.

They are not required with HTML5, but are required by W3C standards in HTML4/XHTML.

Bad code:

<link type="text/css" rel="stylesheet" href="../styles.css">
<script type="text/javascript" src="..//main.js"></script>

Good code:

<link rel="stylesheet" href="styles.css">
<script src="app.js"></script>

Use specific classes when necessary.

Keep the CSS selector more specific and select the elements you need; try not to mention their parents if not necessary. It will allow the code to render faster and remove any managing obstacles in future

Bad code:

section aside h1 span {
  margin-left: 25%;
}

Good code:

.left-offset {
  margin-left: 25%;
}

While applying a class to the targeted element may create more code within HTML, it will allow the code to render faster and will remove any managing obstacles.

Add a class to the parent element if you want to give another style to the same block.

If you need to use the same block but with different styling, try to keep the HTML as untouched as possible. Just add one class to the parent element and apply all new stylings to the children of that class in CSS.

Bad code:

<article>
  <h1>Same section with another styles</h1>
  <p>Lorem ipsum dolor sit amet, lorem sit eget proin id ipsum. </p>
  …
</article>


<article class=”another-article”>
  <h1 class=”other-styling-title”>Same section with another styles</h1>
  <p>Lorem ipsum dolor sit amet, lorem sit eget proin id ipsum. </p>
  …
</article>
article {
  padding: 10px 15px;
  h1 {
    font-size: 32px;
    color: #ff0000;
  }
}

.another-article {
  padding-bottom: 30px;
  border-bottom: 4px solid #ccc;
}
h1.other-styling-title {
  font-size: 20px;
}

Good code:

<article>
  <h1>Same section with another styles</h1>
  <p>Lorem ipsum dolor sit amet, lorem sit eget proin id ipsum. </p>
  … 
</article>


<article class=”other-styling”>
  <h1>Same section with another styles</h1>
  <p>Lorem ipsum dolor sit amet, lorem sit eget proin id ipsum. </p>
  … 
</article>
article {
  padding: 10px 15px;
  h1 {
    font-size: 32px;
    color: #ff0000;
  }

  &.other-styling {
    padding-bottom: 30px;
    border-bottom: 4px solid #ccc;
    /* you will have less classes */
    h1 {
      font-size: 20px;
    }
  }
}

Drop units from zero values.

Adding the unit is unnecessary and provides no additional value. There is no difference between 0px, 0em, 0%, or any other zero-value. The units aren’t important because the value is still zero.

Bad code:

 div {
  margin: 20px 0px;
  letter-spacing: 0%;
  padding: 0px 5px;
}

Good code:

div {
  margin: 20px 0;
  letter-spacing: 0;
  padding: 0 5px;
}

Don’t write border-bottom in CSS if you can add an hr tag.

Use the hr tag instead of writing a new selector and adding border styles in CSS. It makes the code more markup-based, and that’s what we’re going for.

Bad code:

<p class="border-bottom">I want this section to have a border.</p>
<p>Lorem ipsum</p>
.border-bottom {
  border-bottom: 1px solid #000;
}

Good code:

<p>I want this section to have a border.</p>
<hr>
<p>Lorem ipsum</p>
// If necessary change hr variable in bootstrap

Use the A > B selector.

It’s very helpful to use the A > B selector, which applies rules only to direct child(ren), in that case you will not have to reset stylings of all other children which don’t need that styling. For example, it’s very useful when coding nested menus. You will not need to redefine submenu stylings.

Bad code:

<ul>
  <li>List 1</li>
  <li>List 2</li>
  <li>List 3
    <ul>
      <li>Submenu 1</li>
      <li>Submenu 2</li>
    </ul>
  </li>
</ul>
ul li {
  list-style-type: none;
}
li ul li {
  /* redefine to default value */
  list-style-type: disc;
}

Good code:

<ul>
  <li>List 1</li>
  <li>List 2</li>
  <li>List 3
    <ul>
      <li>Submenu 1</li>
      <li>Submenu 2</li>
    </ul>
  </li>
</ul>
ul > li {
  list-style-type: none;
}

How to Write Clean HTML

Moving on to HTML, the priority will be to ensure a robust and easy to maintain front-end.

Try to have a front-end that is as markup-based as possible.

Make your front-end code markup-based instead of writing too much CSS.

This will will help search engines and make your code more readable, potentially enhancing search rankings and user experience.

Bad code:

<div class="main-content">
  <p class="content-title">Main text title</p>
  <img src="http://via.placeholder.com/150x150" alt="example">
  <p class="image-description">Here is image description</p>
</div>

Good code:

<main>
  <h1>Main text title</h1>
  <figure>
    <img src="http://via.placeholder.com/150x150" alt="example">
    <figcaption>
      Here is image description
    </figcaption>
  </figure>
</main>

Avoid using unnecessary wrappers in HTML.

Try not to use unnecessary wrapper elements in HTML. Having tons tons of <div> and <span> elements is a thing of the past. Keeping things granular and linear enables you to achieve minimal code (see next point).

Bad code:

<section class=”container”>
  <div class=”row”>
    <div class=”col-xs-12”>
      <div class=”inner-wrapper”>
        <span>Unnecessary br tags</span>
      </div>
    </div>
  </div>
</section>

Good code:

<section class=”container”>
  <p>Unnecessary br tags</p>
</section>

Use atomic classes for styling.

Do not use any custom colors or font sizes (if the color or font size is not in the framework, just add your atomic classes). Atomic classes are simple, single-purpose units of styling. Much like inline styles, Atomic styles only apply a single style declaration.

Bad code:

<h1>Without using atomic class</h1>
<p>Paragraph without atomic class</p>
<section>
  <h1> Another h1 text</h1>
  <p>Paragraph inside div without class</p>
</section>
h1 {
  color: red;
}
section > h1 {
  color: blue;
}

Good code:

<h1 class="text-red">Without using atomic class</h1>
<p>Paragraph without atomic class</p>
<section>
  <h1 class="text-blue"> Another h1 text</h1>
  <p>Paragraph inside div without class</p>
</section>
.text-red {
  color: red;
}
.text-blue {
  color: blue;
}

Try to keep granular and atomic classes, and use them when needed. Your front-end will become more markup-based as a result.

Make use of semantic elements.

Using semantics provides a better structure and makes the code and content easier to read.

Bad code:

<span class="heading"><strong>Welcome</strong></span>
<span>This is unnecessary br tag.</span>

Good code:

<h1>Welcome</h1>
<p>This is unnecessary br tag.</p>

Use HTML5 tags.

The new tags give you more expressive freedom and to standardize common elements, which improves the automated processing of documents. Here is a solid list of all elements. I’ve discovered that a lot of developers always use a lot of <div> and <span>, but first please check here which tags fit logically in your context:

Bad code:

 <div class="article-blue">
  <div class="article-title-red">HTML 4 title</div>
  <div class="content">
    Semantics
    <span class="boldtext">are</span>
    important.
  </div>
</div>

Good code:

 <article>
  <h1>HTML5 title</h1>
  <p>
    Semantics <strong>are</strong> important.
  </p>
</article>

Bottom line: Learn and use new elements in HTML5. It’s well worth the effort!

CSS: Clean Code and Preprocessors

When it comes to CSS, it’s hard not to start off with some unoriginal, yet sassy advice:

Use a CSS preprocessor.

Sass is the most mature, stable, and powerful professional grade CSS extension language in the world.

There are two syntaxes available for Sass. The first, known as SCSS (Sassy CSS) and used throughout this reference, is an extension of the syntax of CSS. The second and older syntax, known as the indented syntax (or sometimes just “Sass”), provides a more concise way of writing CSS.

Group your selectors: Use @extend in SASS.

By grouping your selectors, or using @extend in SASS, you should help keep your code DRY (Don’t repeat yourself).

Bad code:

p {
   font-size: 22px;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
h1 {
  font-size: 41px;
  color: #000;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

Good code:

.font-smoothing {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

p {
  font-size: 22px;
  @extend .font-smoothing;
}
h1 {
  font-size: 22px;
  @extend .font-smoothing;
}

Use rem units instead of pixels in CSS.

Use REMs for sizes and spacings, for example font sizes it make based on the font-size of the root <html> element. They also allow you to quickly scale an entire project by changing the root font size (for example at a certain media query/screen size).

You will write less code for responsive views:

Bad code:

p {
  font-size: 22px;
  padding: 10px;
}

@media (max-width: 767px) {
  p {
    font-size: 16px;
    padding: 5px;
  }
}

Good code:

html {
  font-size: 14px;
}
@media (max-width: 767px) {
  html {
    font-size: 12px;
  }
}

p {
  font-size: 1.6rem;
  padding: 1rem;
}

Try to avoid having fixed height or width in CSS.

Try to avoid having fixed height or width in CSS. Heights can be generated by having inner content + paddings and widths can be defined by a grid system (use nested grid if necessary).

Bad code:

<footer id="footer" class="text-center">
  <h2>Let's Get In Touch!</h2>
  <hr>
  <p>Ready to start your next project with us?</p>
 </footer>
#footer {
  height: 130px;
}

Good code:

<footer id="footer" class="text-center">
  <h2>Let's Get In Touch!</h2>
  <hr>
  <p>Ready to start your next project with us?</p>
</footer>
#footer {
  padding-top: 23px;
  padding-bottom: 47px;
}

Make sure to use the parent item just once in SCSS.

When you’re using a CSS preprocessor, and planning to write styling for any section, make sure to use the parent item in CSS just once, and include all child elements and media queries inside its brackets. This will allow you to find and modify the main parent element easily in one place when making changes in future.

Bad code:

.section-parent-class {
  position: relative;
  h2 {
      color: #ff0;
    }
  header {
    margin: 2rem 1rem;
  }
}

@media (max-width: 767px) {
  .section-parent-class {
    footer {
      padding: .5rem;
    }
  }
}

Good code:

.section-parent-class {
  position: relative;
  h2 {
    color: #ff0;
  }
  header {
    margin: 2rem 1rem;
  }
  
  footer {
    @media (max-width: 767px) {
      padding: .5rem;
    }
  }
}

Think about which elements will be affected before writing a CSS rule.

Always think about which elements will be affected before writing any CSS rule. If your changes won’t be common, then write your rules in a way to affect only a certain element and nothing else.

Bad code:

/* Commonly used class */
.title {
    color: #008000;
}

Good code:

/* Specify it not to affect other titles */
.section-blog .title {
    color: #008000;
}

Try to look for existing CSS rules and variables before writing new ones.

Always look for existing rules that fit the desired styling, both in custom CSS and in the framework. Only if there is nothing adequate, proceed to writing a new one.

This is particularly important when working with big apps.

Bad code:

.jumbotron {
  margin-bottom: 20px;
}

Good code:

// change $jumbotron-padding from _variables.scss
.jumbotron {
  margin-bottom: $jumbotron-padding;
}

Use specific rules.

If the background has one property, we specify that property, But if it has different background properties, we can give it one declaration.

Bad code:

.selector {
  background: #fff;
}

Good code:

.selector {
  /* This way background image will not be overwritten if there is any */
  background-color: #fff;
}

Use shorthand properties and values.

Do your best to use more shorthand properties and values. Using a shorthand property, you can write concise and, more often than not, much more readable style sheets, saving precious time and energy.

Bad code:

img {
  margin-top: 5px;
  margin-right: 10px;
  margin-bottom: 25px;
  margin-left: 10px;
}
button {
  padding: 0 0 0 20px;
}

Good code:

img {
  /* Shorthand style */
  margin: 5px 10px 25px;
}
button {
  /* Don’t play with shorthand too much */
  padding-left: 20px;
}

Use em instead of px for line height.

Using em and px units gives us flexibility in our designs and the ability to scale elements up and down instead of being stuck with fixed sizes. We can use this flexibility to make our designs easier to adjust during development and more responsive as well as allow browser users to control the overall scale of sites for maximum readability.

Bad code:

p {
  font-size: 12px;
  line-height: 24px;
}

Good code:

p {
  font-size: 12px;
  line-height: 2em; /* or just line-height: 2; */
}

Use Bootstrap classes as much as possible.

While this rule could apply to UI frameworks in general, I am using Bootstrap as an example, because it is the world’s most popular front-end component library.

Bootstrap allows you to use a lot of ready classes, making your work easier. Try to use Bootstrap classes as much as possible, to make more HTML-based markup.

Bad code:

<section class="without-bootstrap">
  <div class="first-column">Lorem ipsum dolor sit amet, ut ius delenit facilisis</div>
  <div class="second-column">Lorem ipsum dolor sit amet, ut ius delenit facilisis</div>
</section>
.first-column,
.second-column {
  width: 25%;
  float: left;
  padding-left: 15px;
  padding-right: 15px;
}
...

Good code:

<section class="row">
  <div class="col-md-6">Lorem ipsum dolor sit amet, ut ius delenit facilisis</div>
  <div class="col-md-6">Lorem ipsum dolor sit amet, ut ius delenit facilisis</div>
</section>

Customize your framework properly.

Bootstrap relies on the variables.scss file, which allows you to easily configure and customize your front-end without writing a single line of code. Otherwise, if you are going to manually write all the customizations on your own, you’d be better not to use a framework at all.

Some developers avoid changing variables.scss, thinking they will still be able to upgrade Bootstrap to a new version without much hassle, but it can be a tedious task. Even minor updates require developers to read the changelog, go through all markup and CSS, and migrate to the new version manually.

Bad code:

navbar {
  padding: 20px 10px;
}
.carousel-indicators {
  li {
    width: 6px;
    height: 6px;
    margin-right: 3px;
    margin-left: 3px;
  }
}

Good code:

$navbar-padding-y:      ($spacer / 2) !default;
$navbar-padding-x:      $spacer !default;

$carousel-indicator-width:       30px !default;
$carousel-indicator-height:      3px !default;
$carousel-indicator-spacer:      3px !default;

Don’t overwrite .container width.

Try to not overwrite .container’s width. Try using a grid system instead, or just change the container width in _variables.scss. If you need to decrease its width, just use max-width instead of width. In that case, .container from Bootstrap will stay untouched in responsive views.

Bad code:

.container {
  @media (min-width: $screen-lg-min) {
      width: 1300px;
  }
}

Good code:

// change $container-lg from _variables.scss
.container {
  @media (min-width: $screen-lg-min) {
    width: $container-lg;
  }
}

Use Bootstrap 4 classes and write less CSS.

Start using Bootstrap 4, even though it’s in beta. It includes many new classes which will help you create layouts faster, especially Flexbox and spacers.

Bad code:

<div class="flex-ex">
  <div>Flex item 1</div>
  <div>Flex item 2</div>
  <div>Flex item 3</div>
</div>
<div class="flex-ex flex-reverse">
  <div>Flex item 1</div>
  <div>Flex item 2</div>
  <div>Flex item 3</div>
</div>
.flex-ex {
  display: flex;
  > div {
    padding: 2px;
  }
  &.flex-reverse {
    flex-direction: row-reverse;
  }
  li {
   list-style: none;
    padding: .5rem;
  }
}

Good code:

 <ul class="list-unstyled list-inline d-flex flex-row">
  <li class="p-2">Flex item 1</li>
  <li class="p-2">Flex item 2</li>
  <li class="p-2">Flex item 3</li>
</ul>
<ul class="list-unstyled list-inline d-flex flex-row-reverse">
  <li class="p-2">Flex item 1</li>
  <li class="p-2">Flex item 2</li>
  <li class="p-2">Flex item 3</li>
</ul>

Now we can assign classes to an element to remove all borders or some borders.

Bad code:

<div class="border-example2 py-5">
  <span class="border0"></span>
  <span class="border-top0"></span>
  <span class="border-right0"></span>
  <span class="border-bottom0"></span>
  <span class="border-left0"></span>
</div>
border-example2 {
  > span {
    width: 100px;
    height: 100px;
    display: inline-block;
    margin: .50rem;
    background-color: #e1e1e1;
    border: 1px solid;
      &.border0 {
        border: none;
      }
      &.border-top0 {
        border-top: none;
      }
      &.border-right0 {
        border-right: none;
      }
      &.border-bottom0 {
        border-bottom: none;
      }
      &.border-left0 {
        border-left: none;
      }
    }
}

Good code:

<div class="border-example py-5">
  <span class="d-inline-block m-2 border-0"></span>
  <span class="d-inline-block m-2 border-top-0"></span>
  <span class="d-inline-block m-2 border-right-0"></span>
  <span class="d-inline-block m-2 border-bottom-0"></span>
  <span class="d-inline-block m-2 border-left-0"></span>
</div>
.border-example {
  > span {
    width: 100px;
    height: 100px;
    background-color: #e1e1e1;
    border: 1px solid;
  }
}

Use .col-sm-X if .col-md-X and .col-lg-X have same value for X.

Don’t write .col-md-X and .col-lg-X if .col-sm-X has same value for X. For example, there is no need to write .col-lg-10 because when we write .col-md-10, we automatically include .col-lg-10 in it.

Bad code:

<ul class="press-list list-inline row">
  <li class="col-lg-4 col-md-4 col-sm-4 col-xs-6"><a href="#"><img class="img-fluid" src="assets/images/press/press-1.png" alt=""></a></li>
  <li class="col-lg-4 col-md-4 col-sm-4 col-xs-6"><a href="#"><img class="img-fluid" src="assets/images/press/press-2.png" alt=""></a></li>
</ul>

Good code:

<ul class="press-list list-inline row">
  <li class="col-md-4 col-xs-6"><a href="#"><img class="img-fluid" src="assets/images/press/press-1.png" alt=""></a></li>
  <li class="col-md-4 col-xs-6"><a href="#"><img class="img-fluid" src="assets/images/press/press-2.png" alt=""></a></li>
</ul>

Don’t use .col-xs-12.

Don’t use .col-xs-12 because if no .col-xs-X is assigned, then it will be .col-xs-12 by default.

Bad code:

<section id="services">
  <div class="container">
    <div class="row">
      <div class="col-lg-12 text-center">
        <h2>Services</h2>
        <h3 class="text-muted">Lorem ipsum dolor sit amet consectetur.</h3>
      </div>
    </div>
    <div class="row text-center">
      <div class="col-md-6 col-xs-12">
        <h4>E-Commerce</h4>
        <p class="text-muted">Lorem ipsum dolor sit amet, consectetur adipisicing.</p>
      </div>
    </div>
  </div>
</section> 

Good code:

<section id="section-services" class="text-center">
  <div class="container">
    <h2>Services</h2>
    <h3 class="text-muted">Lorem ipsum dolor sit amet consectetur.</h3>
    <div class="row">
      <div class="col-md-6">
        <h4>E-Commerce</h4>
        <p class="text-muted">Lorem ipsum dolor sit amet, consectetur adipisicing.</p>
      </div>
    </div>
  </div>
</section> 

Don’t use reset.css; use normalize.css instead.

If you are using Bootstrap, then normalize.css is already included there, no need to include it twice.

Follow guidelines, even if they’re not a best practice.

For the sake of consistency, it’s better to always follow the rules and guidelines you started with (whether they’re about naming, code style, or file structure)

Wrapping Up

I hope you have been able to take away something helpful and that you will think more about writing minimal HTML and CSS code with best practices.

For big companies, it’s quite difficult to maintain big applications when there is messy code. And of course, big companies have the money to pay for good quality. If you follow clean coding principles, you’ll improve your chances of landing a good job. It’s also worth bringing up the freelance aspect: Professionals juggling multiple projects and clients must deliver clean code that can be passed on to other developers in no time.

I hope to expand to more advanced topics in future posts, as I intend to talk about automating the coding process, using Gulp/Grunt tasks, Linters, Editor plugins, tools that generate code, AI tools that write code instead of you, and other related topics.

I hope this has been an interesting and informative read. Good luck in your coding endeavors.

Understanding the basics

What is clean code?

Clean code is code which is easy to read and understand as well as easy for any developer to maintain and modify. It means that assigned names are meaningful but not too long. No parts of code should be repeated, and almost everything should be consistent.

How to write clean code?

"Don't repeat yourself" (DRY), "abstraction," and "keep it simple, stupid" (KISS) principles are the most useful best practices in writing clean code. Consistency and readability should be ensured, and style guides and conventions must be followed.