Overview
By the end of this chapter, you will be able to write CSS code using the BEM approach and explain semantic CSS; describe componentization and rule grouping and apply these principles while writing CSS code; write maintainable SCSS with sensible file and folder structures; and implement CSS best practices to create better maintainable code.
This chapter introduces the concept of maintainable CSS in terms of what it looks like and how we go about creating it. With the knowledge that's given in this chapter, you will be able to create more manageable CSS codebases in SCSS and make your web projects more future-ready for changes and other developers to pick up easily.
In the previous chapters, we learned about a wide variety of different aspects of HTML5 and CSS3, including everything you need to know as a beginner to write HTML5 and CSS3 yourself and develop your first website, but how can we help keep a website maintainable? How do we help prevent the common pitfalls of managing large codebases in CSS? How do we write code that allows a team of developers to work on a website project at the same time and that's also friendly to future developers?
In this chapter, we're going to explore what maintainable CSS looks like, how to write it, and why we use it. Let's start by understanding why we would want to write maintainable CSS.
Say you are working with a large codebase that includes thousands of lines of CSS and the client wants to change a button style that's used throughout the website. Easy, right? Well if we had written maintainable CSS for this project, then we could change this button style in one of the SCSS files that the button component lived in, recompile the CSS, and then the job would be done. But if we didn't write maintainable CSS, updating this button style could mean updating many references in different places to ensure the button is changed globally across the website. This could involve writing overrides to force the button style to change and could end up being very messy and rather unmanageable. We could end up with even more lines of CSS overall for the browser to download, some lines of which probably wouldn't even be required anymore because they're related to the old button style. However, we wouldn't necessarily know where to remove it from if the CSS hasn't been maintained well, causing the codebase to become very bloated with old styles over time.
Good CSS coding practice when working in a team on a website development project is to ensure that any developer can pick up the code where you left off, with all the developers maintaining the same CSS standards to ensure that the codebase makes sense and that modifications can be made quickly, safely, and efficiently to minimize code debt (which, in short, means minimizing unnecessary code). We can understand how projects become unmaintainable if we consider reasons such as budget limitations or other pressures such as tight deadlines, which can make some developers write code that causes CSS code debt, or worse – unmaintainable CSS code.
The first stage of writing maintainable CSS is to write and use meaningful elements identifying rules. A maintainable approach for this is to use Block, Element, and Modifier (BEM). BEM is a semantic approach to identifying elements and applying CSS rules to them.
Semantic CSS means naming elements according to what they are, rather than what they look like. For example, using a class of .article-box instead of .blue-box on an HTML div element would allow the color property of the div to be changed later and it would still make sense semantically, given that the color wasn't associated with the selector name in the first place. Therefore, this helps make the code more maintainable in the future.
Let's explain each block that makes up BEM.
This is the main entity and is meaningful on its own. This could be a header (.header), navigation (.nav), field (.field), article (.article), and so on.
This would be a child of a block, based on a semantic meaning with block, and it wouldn't make sense by itself. Examples include header logo (.header__logo), navigation item (.nav__item), field label (.field__label), and article paragraph (.article__paragraph).
This is a variation in a block or element and can be used to change its appearance or behavior. Examples include a header in a blue theme variation (.header--blue), a navigation item that's highlighted (.nav__item--highlighted), a field with a checkbox input with the state "checked" (.field__input--checked), and a button with a state of success (.button--success).
So, what does BEM look like when written to CSS? The syntax is as follows. To declare a block, you would write just the block name:
.block {}
To modify the appearance or behavior of the block, you would add a modifier:
.block--modifier {}
To style an element of a block, you would use the following syntax:
.block__element {}
To modify the appearance or behavior of an element, you would add a modifier:
.block__element--modifier {}
All of these CSS identifiers refer to the HTML classes that have been assigned and the styles that are inherited this way. Let's look at the HTML syntax of these rules:
<div class="block block--modifier">
<div class="block__element block__element--modifier">
<!-- content here -->
</div>
</div>
Now, let's look at an example of BEM in action. First, we'll take a simple navigation structure and apply BEM to it. Let's start with the HTML:
<nav class="nav">
<ul class="nav__list">
<li class="nav__item">
<a href="#" class="nav__link">Home</a>
</li>
<li class="nav__item">
<a href="#" class="nav__link">About</a>
</li>
<li class="nav__item">
<a href="#" class="nav__link">Contact</a>
</li>
</ul>
</nav>
You will notice how we haven't created "grandchild" selectors such as .nav__item__link as this becomes unnecessarily messy. An element only needs to semantically be related to one block to be maintainable. At this point, it's worth noting that you can create a new block within a block to ensure the code is more semantically relevant.
Now, let's create the CSS for our simple navigation structure:
.nav {
background: black;
}
.nav__list {
display: flex;
}
.nav__item {
list-style: none;
flex: 1 1 auto;
border-right: 1px solid white;
}
.nav__item:last-child {
border-right: none;
}
.nav__link {
display: block;
color: white;
padding: 10px;
text-align: center;
text-decoration: none;
}
.nav__link:hover {
text-decoration: underline;
}
Here, you can see what the CSS for the BEM marked up navigation looks like. The following screenshot shows how this would be displayed in the browser:
Now, let's say we wanted to change an instance of the navigation and make the contact navigation link stand out more. First, we'd give the link a modifier class:
<a href="#" class="nav__link nav__link--contact">Contact</a>
Then, we can add a new CSS rule to make the link stand out more:
.nav__link--contact {
color: red;
font-weight: bold;
}
This would update the navigation to appear as follows:
If we wanted to change the appearance of all the links, for instance, use different colors and fonts for navigation that appears on a different part of the website, then we could apply a modifier to the block itself, rather than the individual element. The HTML could be changed for .nav by adding a new modifier called theme2, like so:
<nav class="nav nav--theme2">
We would then create a new CSS rule to apply the theme2 styling to the navigation bar. This would look something like this:
.nav--theme2 {
background: lightgray;
}
.nav--theme2 .nav__link {
background: lightgray;
color: black;
font-family: Arial;
}
The following screenshot shows the end result of the theme2 modifier being applied, the .nav styles being inherited, and the modifier changing/extending the styles to give a different appearance:
Next, we'll look at putting BEM markup into action by practicing its use in an exercise.
The aim of this exercise is to get you writing maintainable BEM code, starting with some existing HTML and CSS markup, and transforming this into a more maintainable semantic BEM-based markup. Let's get started:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Chapter 11: Exercise 11.01</title>
<link href="css/exercise1.css" rel="stylesheet" />
</head>
<body>
<header class="header">
<div class="logocontainer">
<img class="logocontainer__img" src="https://dummyimage.com/200x100/fff/000&text=Logo" alt="Logo" />
</div>
<nav class="nav nav--header">
<ul class="nav__list">
<li class="nav__item">
<a class="nav__link" href="#">Home</a>
</li>
<li class="nav__item">
<a class="nav__link" href="#">About</a>
</li>
<li class="nav__item">
<a class="nav__link" href="#">Contact</a>
</li>
</ul>
</nav>
</header>
<nav class="nav nav--subnav">
<ul class="nav__list">
<li class="nav__item">
<a class="nav__link" href="#">Sub Page 1</a>
</li>
<li class="nav__item">
<a class="nav__link" href="#">Sub Page 2</a>
</li>
<li class="nav__item">
<a class="nav__link" href="#">Sub Page 3</a>
</li>
</ul>
</nav>
<article class="article">
<h1 class="article__title">Article Title</h1>
<p class="article__text"><img class="article__image" src="https://dummyimage.com/200x100/fff/000&text=Demo+Image" alt="Demo Image" /> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam quis scelerisque mauris. Curabitur aliquam ligula in erat placerat finibus. Mauris leo neque, malesuada et augue at, consectetur rhoncus libero. Suspendisse vitae dictum dolor.</p>
<p class="article__text">Vestibulum hendrerit iaculis ipsum, ac ornare ligula. Vestibulum efficitur mattis urna vitae ultrices. Nunc condimentum blandit tellus ut mattis. <img class="article__image article__image--right" src="https://dummyimage.com/200x100/fff/000&text=Demo+Image+2" alt="Demo Image 2" /> Morbi eget gravida leo. Mauris ornare lorem a mattis ultricies. Nullam convallis tincidunt nunc, eget rhoncus nulla tincidunt sed.</p>
<p class="article__text">Nulla consequat tellus lectus, in porta nulla facilisis eu. Donec bibendum nisi felis, sit amet cursus nisl suscipit ut. Pellentesque bibendum id libero at cursus. Donec ac viverra tellus. Proin sed dolor quis justo convallis auctor sit amet nec orci. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</p>
</article>
<footer class="footer">
<p class="footer__copyright">Copyright Text Goes Here</p>
<nav class="nav nav--footer">
<ul class="nav__list">
<li class="nav__item">
<a class="nav__link" href="#">Footer Nav 1</a>
</li>
<li class="nav__item">
<a class="nav__link" href="#">Footer Nav 2</a>
</li>
<li class="nav__item">
<a class="nav__link" href="#">Footer Nav 3</a>
</li>
</ul>
</nav>
</footer>
</body>
</html>
As you can see, all the navigation instances share the same .nav styles initially, but each unique instance has a modifier class (for instance, .nav--header) to tweak its appearance for that instance. We've added a modifier class to each of the reused nav blocks (for instance, .nav--header), so that we can reuse the .nav block as a reusable component. It's default styling rules (from .nav) can be inherited on the header (.nav--header), sub navigation (.nav--subnav), and footer (.nav--footer).
* {
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
font-size: 14px;
color: #000;
}
.header {
display: flex;
align-items: center;
}
.logocontainer {
flex: 0 0 auto;
}
.logocontainer__img {
width: 100%;
height: auto;
display: block;
border: 1px solid #000;
}
.nav--header {
flex: 1 1 auto;
text-align: right;
padding-right: 20px;
}
.nav__list {
list-style: none;
}
.nav__item {
display: inline-block;
padding-right: 10px;
}
.nav__item:after {
content: ' 2022';
padding-left: 10px;
}
.nav--subnav .nav__item:last-child {
padding-right: 0;
}
.nav__item:last-child:after {
display: none;
}
.nav__link {
color: #000;
text-decoration: none;
}
.nav__link:hover {
text-decoration: underline;
}
.nav--subnav {
text-align: center;
background: #000;
padding: 10px 0;
}
.nav--subnav .nav__item {
color: #FFF;
}
.nav--subnav a {
color: #FFF;
}
.article {
margin: 20px;
}
.article__image {
float: left;
margin: 0 10px 10px 0;
border: 1px solid #000;
}
.article__image--right {
float: right;
margin: 10px 0 0 10px;
}
.footer {
background: #DDD;
clear: both;
display: flex;
padding: 20px;
}
.footer__copyright {
flex: 1 1 auto;
}
.nav--footer {
flex: 0 0 auto;
text-align: right;
}
You will notice that when using BEM in CSS, not much nesting is required, as we only need to nest when modifiers are used at a parent level.
We've now completed this exercise and you should have a good understanding of what BEM markup is and how we can take existing HTML and CSS markup and convert it into the more maintainable BEM markup standard. Next, we're going to review how we can use BEM in SCSS.
Developing our knowledge further from Chapter 10, Preprocessors and Tooling, we're going to take a look at how we can write BEM in our SCSS code so that we can compile it into more maintainable CSS code. We can nest BEM elements inside the block with SCSS. For example, if we had the .nav__list element, then we could nest it inside a .nav block using the & syntax, as shown in the following example:
.nav {
&__list {
/* Styles Here */
}
}
Let's look at the sample CSS we had earlier in this chapter:
.nav {
background: black;
}
.nav__list {
display: flex;
}
.nav__item {
list-style: none;
flex: 1 1 auto;
border-right: 1px solid white;
}
.nav__item:last-child {
border-right: none;
}
.nav__link {
display: block;
color: white;
padding: 10px;
text-align: center;
text-decoration: none;
}
.nav__link:hover {
text-decoration: underline;
}
To achieve the preceding CSS output, we would write the following SCSS:
.nav {
background: black;
&__list {
display: flex;
}
&__item {
list-style: none;
flex: 1 1 auto;
border-right: 1px solid white;
&:last-child {
border-right: none;
}
}
&__link {
display: block;
color: white;
padding: 10px;
text-align: center;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
The SCSS is 9 lines and 146 characters more compact than the original CSS, so it's quicker to write and easier to maintain as it's still using BEM. As we discussed in Chapter 10, Preprocessors and Tooling, we can get our npm run scss command to output our CSS in compressed format too, thereby making the production website version super compact but keeping the SCSS file easily maintainable.
Next, we're going to look at adding the modifier class to the SCSS file. For example, if we had a modifier class of .nav__link--contact, then we could add this nested part of the &__link part of the SCSS, as follows:
.nav {
&__link {
&--contact {
color: red;
font-weight: bold;
}
}
}
The preceding SCSS code would construct the .nav__link--contact class and apply the bold format and red color to the text. If we added a modifier class to the block called .nav--theme2, and if we wanted to change the appearance of an element within the modified block, then we could nest the block modifier within the element SCSS, as shown in the following code:
.nav {
&__link {
.nav--theme2 & {
background: lightgray;
color: black;
font-family: Arial;
}
}
}
As you can see in the preceding code, we are changing the font, font color, and background color of .nav__link if it's preceded by the .nav--theme2 block modifier class. We're going to put this into practice in the next exercise by updating our CSS and making it into SCSS.
The aim of this exercise is to put writing SCSS in BEM markup into practice so that we can make our code even more maintainable. We'll use the HTML and CSS code we wrote in Exercise 11.01 as the basis to begin this exercise and then we'll rewrite the CSS so that it can be compiled from SCSS instead. Let's get started:
<link href="css/exercise2.css" rel="stylesheet" />
* {
margin: 0;
padding: 0;
}
body {
font-family: Arial, sans-serif;
font-size: 14px;
color: #000;
}
.header {
display: flex;
align-items: center;
}
.logocontainer {
flex: 0 0 auto;
&__img {
width: 100%;
height: auto;
display: block;
border: 1px solid #000;
}
}
.nav {
&--header {
flex: 1 1 auto;
text-align: right;
padding-right: 20px;
}
&--subnav {
text-align: center;
background: #000;
padding: 10px 0;
}
&--footer {
flex: 0 0 auto;
text-align: right;
}
&__list {
list-style: none;
}
&__item {
display: inline-block;
padding-right: 10px;
&:after {
content: ' 2022';
padding-left: 10px;
}
&:last-child {
padding-right: 0;
}
&:last-child:after {
display: none;
}
.nav--subnav & {
color: #FFF;
}
}
&__link {
color: #000;
text-decoration: none;
&:hover {
text-decoration: underline;
}
.nav--subnav & {
color: #FFF;
}
}
}
.article {
margin: 20px;
&__image {
float: left;
margin: 0 10px 10px 0;
border: 1px solid #000;
&--right {
float: right;
margin: 10px 0 0 10px;
}
}
}
.footer {
background: #DDD;
clear: both;
display: flex;
padding: 20px;
&__copyright {
flex: 1 1 auto;
}
}
In the preceding SCSS code, inside every block are the elements and some modifier classes. This will create the same CSS output as exercise1.css does when it's compiled into exercise2.css. The original CSS file was 112 lines; we've written 109 lines of SCSS to replace it. When output into exercise2.css in a compressed output style, it's only 1 long line, which is super optimized for use on a production website. However, you've still got the SCSS file so that you can make edits, which makes this very maintainable.
{
"name": "chapter11",
"version": "1.0.0",
"description": "HTML5 & CSS3 Workshop Chapter11 Exercises",
"main": "index.js",
"scripts": {
"test": "echo "Error: no test specified" && exit 1",
"scss": "node-sass --watch scss -o css --output-style compressed"
},
"author": "Matt Park",
"license": "ISC",
"dependencies": {
"node-sass": "^4.12.0"
}
}
Now that we've completed this exercise, you should be able to write SCSS in the maintainable BEM markup standard and understand how CSS can be converted into nested SCSS and vice versa. In the next section of this chapter, we'll look at structuring our SCSS files into an even more maintainable format.
In this section, we're going to explore how to make our SCSS more maintainable by splitting the code into separate files and folders, which is very useful when managing large code bases of CSS.
It is recommended to group SCSS rules together into blocks or components. For example, the navigation would have its own SCSS file, and all of those SCSS files would be merged into a single CSS file output when compiled.
Each individual SCSS file would have an underscore in front of the filename to prevent it from being individually compiled. This means we can create the _navigation.scss file and add our navigation styles to it. Inside our main file (main.scss), which is going to be output into CSS, we can list all the components we are going to import, like so:
/* Layout File */
@import '_header';
@import '_navigation';
@import '_footer';
With the example layout.scss file, this would output to layout.css, along with the contents of the _header, _navigation, and _footer SCSS files merged in.
It's good practice to ensure that the different types of SCSS code belong in their appropriate folder groups. We recommend using a file structure similar to the one shown in the following diagram:
As you can see, we have the following folders:
We also have main.scss, which is where all the mentioned folders would be imported. We can achieve this with a wildcard folder import so that as we add new files to the existing folders, they are imported without changing the main.scss file. See the suggested contents of main.scss in the following code, based on the folders in the preceding diagram :
@import "abstracts/_variables";
@import "abstracts/_mixins";
@import "base/_reset";
@import "base/_typography";
@import "components/_buttons";
@import "layout/_header";
@import "layout/_navigation";
@import "layout/_footer";
This code will get all the other SCSS code from these files and output via a single file called main.css.
The aim of this exercise is to create SCSS file structures using a maintainable approach. We'll use the SCSS code we developed for Exercise 11.02 and split it into the appropriate files and folders in order to create a maintainable code base for the website to grow on. Let's get started:
<link href="css/exercise3.css" rel="stylesheet" />
scss/exercise3/base/_reset.scss:
* {
margin: 0;
padding: 0;
}
scss/exercise3/base/_typography.scss:
body {
font-family: Arial, sans-serif;
font-size: 14px;
color: #000;
}
scss/exercise3/components/_article.scss:
.article {
margin: 20px;
&__image {
float: left;
margin: 0 10px 10px 0;
border: 1px solid #000;
&--right {
float: right;
margin: 10px 0 0 10px;
}
}
}
scss/exercise3/layout/_header.scss:
.header {
display: flex;
align-items: center;
}
.logocontainer {
flex: 0 0 auto;
&__img {
width: 100%;
height: auto;
display: block;
border: 1px solid #000;
}
}
The scss/exercise3/layout/_navigation.scss code file can be found at https://packt.live/36XoXnB.
scss/exercise3/layout/_footer.scss:
.footer {
background: #DDD;
clear: both;
display: flex;
padding: 20px;
&__copyright {
flex: 1 1 auto;
}
}
@import 'exercise3/base/_reset';
@import 'exercise3/base/_typography';
@import 'exercise3/layout/_header';
@import 'exercise3/layout/_navigation';
@import 'exercise3/layout/_footer';
@import 'exercise3/components/_article';
This will import all the SCSS subfiles we just created.
scss/exercise3/abstracts/_variables.scss
$primary-color: #004275;
$secondary-color: #ffd421;
$tertiary-color: #00e1ea;
@import 'exercise3/abstracts/_variables';
Note that it's very important to ensure that this is placed at the top of the scss/exercise3.scss file. This ensures that the variables are defined before we run the other SCSS code.
We've now got a maintainable SCSS file structure with variables, reset styles, base typography styles, layout styles for the header, footer, navigation, and component styling for an article. This will be much more manageable as the website grows with components, pages, and styling rules. If we hadn't applied these best practices, it would be harder to manage the website project as it grows with more and more styles.
Since we can group the rules into components and different blocks, if we remove a certain component from the website in the future, cleaning up the styles would be as simple as removing the SCSS file for that component and removing the import line from the main SCSS file as well. This clean add/remove approach to new CSS files for blocks and components greatly reduces the amount of technical debt created over time as the project grows in size.
There are several other good practices to follow in addition to using BEM with SCSS in structured folders and files. There are six points to consider when creating maintainable CSS:
%font-normal {
font-family: Arial, sans-serf;
font-size: 14px;
color: #000;
}
body {
@extend %font-normal;
background: #CCC;
}
input[type=text] {
@extend %font-normal;
border: 1px solid #000;
}
This generates the following CSS:
body,
input[type=text] {
font-family: Arial, sans-serf;
font-size: 14px;
color: #000
}
body {
background: #CCC
}
input[type=text] {
border: 1px solid #000
}
As you can see, wherever we use the extend class, it groups together and outputs in one go in the CSS. However, it's easier to maintain in SCSS as we still edit the code in its actual rule location, and so we don't have to create multiple rules for the same code – it's all handled upon the compilation of it.
.unordered-selector {
width: 100px;
font-family: Arial;
background: black;
color: white;
padding: 5px;
text-decoration: underline;
height: 100px;
border: 1px solid white;
font-size: 20px;
}
With the following ordered example, you can see that the properties follow a grouping order, that is, of the type of properties, thus resulting in more maintainable CSS:
.ordered-selector {
width: 100px;
height: 100px;
padding: 5px;
font-family: Arial;
font-size: 20px;
color: white;
text-decoration: underline;
background: black;
border: 1px solid white;
}
When CSS gets out of control with the deep nesting of rules (for instance, beyond three nesting levels deep), it's often very easy for a new developer to come into the website project and add an !important flag onto the end of various CSS properties so that they can use a shorter CSS selector (for instance, one or two nesting levels deep) in order to override a value with something else.
You can imagine that, after a while, this becomes very messy, and soon becomes difficult to maintain, with various CSS selectors trying to override each other with the !important flag. Use this very sparingly to avoid a maintenance disaster.
We've now covered various aspects of writing maintainable CSS, including understanding what is meant by the phrase semantic CSS, writing CSS, SCSS with the BEM approach, understanding componentization and rule grouping, writing maintainable SCSS with sensible file and folder structures, and finally, following good CSS practices to generally create more maintainable code. We'll now look at applying our knowledge of maintainable CSS to our activity. We'll do this by updating our video store home page with the new techniques we've learned about.
The aim of this activity is to take our existing video store home page (from Chapter 10, Preprocessors and Tooling, Activity 10.01, Converting a Video Store Home Page into SCSS) and refactor the code so that it uses BEM semantic markup with suitable SCSS file structuring to create a more maintainable web page. Let's get started:
Note
The complete solution can be found in page 616.
In this chapter, we covered various aspects of creating and using maintainable CSS in our work. First, we got BEM markup for our HTML and CSS and then we looked at how SCSS can work with BEM and how to separate its folders and files to enable better CSS codebase management at a larger scale. We also reviewed additional good practices to help us write maintainable CSS. You should now have an idea of what maintainable CSS code looks like and how you can write your CSS to follow the same standards.
In the next chapter of this book, we will learn about web components and how APIs, when combined, can create powerful features on a web page.