...
Email
Telephone
section elements from black to gray, but the concerned CSS selector is named, for instance, .content. Such a name is not intuitive based on the task it should perform (formatting the section element) and narrowing our search down to this particular selector becomes nearly impossible if the code is not commented.
This section will discuss a few techniques, tips, suggestions, and commonly adopted practices that will help us write clean, manageable, and beautiful stylesheets. We will group these best practices based on the following broad categories: General Practices, Organization, IDs, Classes, and Selectors, Properties, Shorthand Notation, and Miscellaneous Tips.
/* BAD */
p {
color: #C0C0C0;
font-family: 'Verdana'
}
/* GOOD */
p {
color: #C0C0C0;
font-family: 'Verdana';
}
A semicolon is technically not required for the last of multiple rules, but adding it is a good practice. Leaving it off does not create an issue if the declaration has just a single rule, but if it has multiple rules, a semicolon is required at the end of every rule. As such, always treat the semicolon as a terminator (end of a rule) and not as a separator (merely separating two rules).
For instance, imagine a developer decides to add a third property after font-family to the above CSS. If the semicolon after font-family: 'Verdana' is missing, the CSS code won't compile. Because the previous developer treated the semicolon as a separator, not as a terminator, and left it off, the developer adding to the new code must be sure to address the missing semicolon in order for it to compile correctly, as shown below:
/* BAD - Missing semicolon breaks the code*/
p {
color: #C0C0C0;
font-family: 'Verdana'
margin: 10px;
}
/* GOOD - Semicolon after every property*/
p {
color: #C0C0C0;
font-family: 'Verdana';
margin: 10px;
}
Hence, for the sake of consistency, clarity, and soundness of code, we consider adding semicolons to all the rules within a rule-set to be best practice.
/* BAD */ and /* GOOD */ clearly demarcates the code and indicates the bad and the good code snippets.
/* BAD */
html {
background-color: #ffe5ff;
font-family: sans-serif;
}
.static-block{
position: static;
}
.fixed-block{
position: fixed;
right: 0px;
top: 0px;
}
/* GOOD */
/*Page-wide Style Rules*/
html {
background-color: #ffe5ff;
font-family: sans-serif;
}
/*Styling for Static Block*/
.static-block{
position: static;
}
/* Styling for Fixed Block*/
.fixed-block{
position: fixed;
right: 0px;
top: 0px;
}
Although comments are not mandatory for making CSS work, they are very helpful to the folks presently working on the code and those who will work on it in the future. Making your code self-explanatory is a good practice and benefits everyone on a team in the long run.
style attribute.head section of an HTML page within the style tags.link element within the head section.
/* BAD */
Fixed Block
Static Block
Out of the three approaches, inline styling should be used as a last resort because it:
/* GOOD */
Fixed Block
Static Block
Following this practice of separating CSS code from the HTML markup, we simply reference the appropriate CSS classes using selectors. This not only avoids clutter in the HTML document but also makes updating the style rules at one place (in the CSS file) easy.
/* BAD */
.static-block{
color: #0000FF;
position: static;
}
.fixed-block{
color: #0000FF;
position: fixed;
}
/* GOOD */
.static-block{
position: static;
}
.fixed-block{
position: fixed;
}
.block-background-default {
color: #0000FF;
}
Instead of repeating the same color property in two class declarations, we can create a new class named block-background-default to render the blocks in default color (blue, in this case). This class can now be used in conjunction with any other existing classes.
reset.css, header.css, content.css and footer.css. We can import all these styles in the master CSS, as shown below:
/* master.css */
@import url("reset.css");
@import url("header.css");
@import url("content.css");
@import url("footer.css");
Each CSS file imported in the master.css serves a specific purpose:
reset.css file contains a set of rules used to remove the browser's built-in styles. They are loaded before any other style rules to get around any browser inconsistencies in rendering the default styles. CSS Resets are discussed further later in this chapter.header.css file contains CSS related to the styling of the components that belong to the page header. For instance, the CSS to style any navigation bars, login component, company logo, search panel, or breadcrumbs should be placed here.content.css file contains CSS related to the contents of the page. Styling for HTML elements, such as section, article, or aside, or for selectors used to target elements within the body of the page are placed here.footer.css file contains CSS related to the styling of the components that belong to the page footer.master.css consolidates all the required CSS resources, we can simply use the following code to import the CSS on our page:
/* Import master.css */
@import url("master.css");
header_mobile.css and wish to utilize it for viewports smaller than 320 px, media queries can be used to import this CSS file, as shown below:
/* master.css */
@import url("reset.css");
@import url("content.css");
@import url("header_mobile.css")
/* More than 320px */
@import url("header.css") only screen and (min-width: 320px);
@import url("footer.css");
/* BAD */
#navbar a:link {
color: white;
text-decoration: none;
}
html {
background-color: #7BC143;
color: white;
font: 75% sans-serif;
text-align:center;
}
#navbar a{
border-right: 1px solid;
color: inherit;
display:inline;
padding: 10px;
}
.reftext {
font-size: 18px;
}
#navbar
{
background-color: #D61F57;
margin-left: auto;
margin-right: auto;
width: 50%;
}
header{
color: black;
font-size: 150%;
}
Let's fix this and make use of CSS comments to arrange our code:
/* GOOD */
/* ********** Page-wide CSS styles ********* */
html {
background-color: #7BC143;
color: white;
font: 75% sans-serif;
text-align:center;
}
/* ********** HTML header element CSS styles ********* */
header{
color: gray;
font-size: 150%;
}
/* ********** Reference text CSS styles ********* */
.reftext {
font-size: 18px;
}
/* ********** Navigation bar related CSS styles ********* */
#navbar
{
background-color: #D61F57;
margin-left: auto;
margin-right: auto;
width: 50%;
}
#navbar a{
border-right: 1px solid;
color: inherit;
display:inline;
padding: 10px;
}
#navbar a:link {
color: white;
text-decoration: none;
}
As we can see, all the related rules are kept together, which makes reading and understanding the code simpler. Also, the rules are arranged in increasing order of specificity under a block. This makes it easy to locate any element on the DOM and relate it back to the CSS.
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
@import url("https://fonts.googleapis.com/css?family=Montserrat");
body {
font-family: 'Helvetica Neue', Helvetica, sans-serif;
}
html,
body {
height: 100%;
}
input#show-menu {
position: absolute;
opacity: 0;
}
#fix-bar {
position: fixed;
top: 0;
left: 0;
z-index: 9;
width: 100%;
height: 60px;
background-color: #00a8eb;
}
#fix-bar .logo a {
display: block;
color: #fff;
text-decoration: none;
text-transform: uppercase;
line-height: 24px;
width: 60px;
height: 24px;
padding: 18px 16px 18px 24px;
box-sizing: content-box;
}
#fix-bar .logo {
position: absolute;
left: 0;
width: 250px;
transform: translate3d(-50%, 0, 0);
transition: transform 0.5s ease;
}
#fix-bar .push {
position: relative;
left: 0;
height: 100%;
background-color: #00a8eb;
transition: transform 0.5s ease;
}
#canvas {
position: relative;
height: 100%;
padding-top: 60px;
overflow: hidden;
}
#nav {
overflow-y: scroll;
position: absolute;
left: 0;
height: 100%;
width: 250px;
transform: translate3d(-50%, 0, 0);
transition: transform 0.5s ease;
padding-top: 12px;
padding-bottom: 60px;
}
#nav .site-menu {
position: relative;
min-height: 100%;
padding-bottom: 60px;
}
#nav a {
text-decoration: none;
color: #000;
display: block;
padding: 14px 24px;
font: 20px/1 'Montserrat', sans-serif;
font-weight: 700;
}
#nav a:hover {
background-color: #eee;
}
#nav .copy {
position: absolute;
bottom: 0;
padding: 16px 24px;
font-size: 12px;
line-height: 1.4;
}
input#show-menu:checked ~ #fix-bar .push {
transform: translate3d(250px, 0, 0);
}
#content {
background-color: #fff;
overflow-y: scroll;
position: relative;
left: 0;
height: 100%;
transition: transform 0.5s ease;
transform: translate3d(0, 0, 0);
padding: 20px;
}
input#show-menu:checked ~ #canvas #content {
transform: translate3d(250px, 0, 0);
}
.mask {
position: absolute;
top: 60px;
left: 0;
z-index: -1;
width: 100%;
height: 100%;
opacity: 0;
transition: opacity 0.5s ease, transform 0.5s ease, z-index 0s 0.5s;
background-color: rgba(0,0,0,0.5);
}
input#show-menu:checked ~ #canvas #nav,
input#show-menu:checked ~ #fix-bar .logo {
transform: translate3d(0, 0, 0);
}
input#show-menu:checked ~ #canvas .mask {
z-index: 1;
opacity: 1;
transform: translate3d(250px, 0, 0);
transition: opacity 0.5s ease, transform 0.5s ease;
}
Refactor this CSS according to the specifications mentioned above.
bar, but you have no idea whether it refers to a navigation bar, a footer bar, or some other bar-like component on the page. This class name is not only generic; it gives the user no clue about its functionality. Comments do come to the rescue in such scenarios, but again, not everyone comments CSS code in detail. Hence, it becomes very important to use descriptive names and a naming convention while writing CSS.
profile-avatar, profile-info or profile-menu. This also helps avoid possible naming conflicts. Creating all three classes as simply profile would cause a conflict of rules.
.button-green gives anyone reading the code an idea that the button will be green. If the color of the button happens to change to blue, the class name will inaccurately reflect the true color of the button:
/* BAD */
.button-green {
background-color: #00FF00;
border: 1px solid #00FF00;
}
To address this, the class name must be changed, a new class for the blue button will need to be created, or additional documentation must be provided to convey the real meaning of the button. To avoid this hassle, we can create a class with the name .button-success, a name that labels the use of the button without describing it:
/* GOOD */
.button-success{
background-color: #00FF00;
border: 1px solid #00FF00;
}
This class can be used to create a green-colored button or a button of any other color. If the specification for the class changes in the future, no new class needs to be created to accommodate this change.
/* BAD */
.div .span {
color: #C0C0C0;
}
/* CORRESPONDING HTML*/
Container Text
The resulting HTML will create confusion because the tag names and the CSS class names are the same. The best practice is to use descriptive names to avoid naming chaos.
/* GOOD */
.container .container-text{
color: #C0C0C0;
}
/* CORRESPONDING HTML*/
Container Text
In the above snippet, we can clearly make sense of the structure by looking at the class names.
#navbar a.section > header.article ~ div.article + span.div structure. Let's look at the following example to understand the usefulness of this practice:
...
The above snippet does not make use of modern HTML tags, such as nav, section, and article. This results in creating an ID selector for every level of the nested structure, as shown:
/* CSS */
#navigation{ ... }
#navigation-content { ... }
#section { ... }
#subsection { ...}
.content { ... }
Instead, the new HTML5 tags should be used to create the nested structure, which will, in turn, reduce the number of CSS IDs used (like the ones preceded by # in the above examples). The following snippet shows the redesigned HTML structure:
...
/* CSS */
#navigation-content { ... }
.article-content { ... }
A navigation bar ideally appears just once on a web page. Hence, it can be uniquely styled using the id selector #navigation-content while the class selector .article-content formats the content within an article residing inside a section. This method makes the selector less specific and fully self-explanatory.
h1, h2, and h3 type selectors, but if we wish to write specific rules for h3, we must write it as a separate rule:
/* BAD */
h1 {
color: #C0B4F8;
font-family: 'Verdana';
}
h2 {
color: #C0B4F8;
font-family: 'Verdana';
}
h3 {
color: #C0B4F8;
font-family: 'Verdana';
}
/* GOOD */
h1, h2, h3 {
color: #C0B4F8;
font-family: 'Verdana';
}
h3 {
border: 1px solid #C0C0C0;
}
This concludes our discussion on naming conventions involving selectors.
/* CSS code*/
#nav {
overflow-y: scroll;
position: absolute;
left: 0;
height: 100%;
width: 250px;
transform: translate3d(-50%, 0, 0);
transition: transform 0.5s ease;
padding-top: 12px;
padding-bottom: 60px;
}
#nav a {
text-decoration: none;
color: #000;
display: block;
padding: 14px 24px;
font: 20px/1 'Montserrat', sans-serif;
font-weight: 700;
}
#nav a:hover {
background-color: #eee;
}
#nav .site-menu {
position: relative;
min-height: 100%;
padding-bottom: 60px;
}
#nav .copy {
position: absolute;
bottom: 0;
padding: 16px 24px;
font-size: 12px;
line-height: 1.4;
}
Perform the following tasks on the above snippet:
nav tag instead of the id="nav" attribute.nav and use the type selector instead. Make the necessary changes to all the child elements as well.class="has-sub" attribute. Add the following properties to it: font size of 12 pt with a transition effect of fade upon hover.section-subheader selector initially do not have a specific order:
/* BAD */
.section-subheader
{
background: #EFF5FB;
padding: 10px;
border: dashed 2px maroon;
margin-bottom: 20px;
width: 40%;
}
However, sorting them alphabetically makes it easier to find a property later. A developer wanting to add a border to this selector will easily notice that the property already exists:
/* GOOD */
.section-subheader
{
background: #EFF5FB;
border: dashed 2px maroon;
margin-bottom: 20px;
padding: 10px;
width: 40%;
}
While alphabetizing may not make much difference for a selector that has few properties within a rule, arranging them alphabetically does help when several properties are present. This practice of ordering properties alphabetically also indirectly influences the next developer working with the code to add new properties in the correct alphabetical order.
/* BAD */
.menu {
background-color: beige;
font-family: verdana;
}
.menu li {
background-color: beige;
display: inline-block;
font-family: verdana;
position: relative;
}
/* GOOD */
.menu {
background-color: beige;
font-family: verdana;
}
.menu li {
display: inline-block;
position: relative;
}
The child elements (in this case, the li being a child of the element with class="menu") inherit all the properties defined for the parent element (except the background-color element). Unless we need to override a certain style declaration for a child element, we should avoid defining it again. In the example above, defining background-color and font-family again for the li introduces repeated code.
The background-color property is not inherited because the default color is set to transparent for the child elements. As a result, the background color of the parent element, .menu, is visible for the child element, .menu li.
Free tools, such as Unused CSS or uCSS, are available that will parse your HTML and CSS. These tools significantly reduce the number of style rules by deleting the unused rules and their properties, and this results in smaller CSS files.
!important!important property was introduced to give developers the power to override style rules within a stylesheet. It interferes with a core CSS concept called specificity which we discussed in the Mastering CSS chapter. The specificity score helps the browser decide the exact selector that will be applied to an element when the browser is unable to decide the exact selector to be applied to an element in the DOM. Consider the following snippet, which has rules set up for the HTML header tag:
header {
color: blue;
}
header .page {
color: red !important;
font-size: 18px;
}
header #box {
color: green;
font-size: 15px;
}
The header color is blue in all cases except when the class is page or the ID is box. Based on the specificity of the selector, the appropriate rule is selected.
The !important clause is applied at the property level, not at the selector level; giving the color property more importance than font-size within a selector. In fact, the rule having greater specificity, header #box, bows down to the !important clause. This clause should be used as scarcely as possible, primarily because it forcefully overrides any previously declared rules.
html {
font-family: Georgia;
background-color: #ffe5ff;
}
.subheading {
font-family: Georgia;
text-decoration: underline;
border: 1px solid;
}
#navBar ul li {
display: inline;
padding: 10px;
border-right: 1px solid;
color: orange;
}
#navBar ul li a:link {
text-decoration: none;
}
#navBar ul li a:visited {
text-decoration: none;
color: orange;
}
#navBar ul li a:hover {
text-decoration: none;
color: black;
}
#navBar ul li a:active {
text-decoration: none;
color: orange;
}
input:checked {
font-family: Georgia;
font-size: 15pt;
display: none;
}
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
Use a CSS reset either by placing the source file link in your HTML file or by copying the whole CSS reset into your stylesheet.
CSS Reset is often confused with CSS Normalization. Although the purpose of both is fundamentally the same---creating a stable, cross-browser starting point for our CSS---the two approaches are philosophically different. CSS resets override the browser (or user-agent) styles to zero out as much as possible in order to create a stable foundation to build upon. CSS normalization, instead, addresses only the inconsistencies between user/agent styles and, in case of differences, chooses the most appropriate style. Normalize.css is a widely used resource created by Nicholas Gallagher.
If we wish to preserve browser defaults, we should use Normalize.css. However, to "unstyle" everything and start with a blank canvas, we should use a CSS Reset. For example, when using a CSS Reset, elements like h1 through h6, em, and strong appear without any predefined styles. A Reset will strip any styling from the elements so that the developer has more control of the CSS from the start.
On the other hand, Normalize.css applies a set of styles to each element to make them look consistent across all the browsers. For example, Normalize.css makes all the input elements have the same font. CSS Reset does not have pre-built styles, so inputs have different fonts (for instance, text input and textarea)..
border, padding, and margin, etc., can be represented by their shorthand notations. Let's look at a couple of these properties:
/* BAD */
/* Expanded representation */
border-color: black;
border-style: solid;
border-width: 1px;
/* GOOD */
/* Shorthand notation */
border: 1px solid black;
As shown above, instead of specifying three different properties to draw a border, we can easily consolidate them into a single line of code. The same applies to the CSS padding and margin properties as well:
/* BAD */
/* Expanded representation */
padding-bottom: 2px;
padding-left: 5px;
padding-right: 2px;
padding-top: 1px;
/* GOOD */
/* Shorthand notation */
padding: 1px 2px 2px 5px;
For padding, margin, and border properties, the general rule for shorthand notation is:
margin: top right bottom left;
OR
padding: top right bottom left;
OR
border: top right bottom left;
If we know all four values for any of these properties for an element, as a best practice, we should combine them into a single line of code. We can also specify anywhere between one to four values in the shorthand notation. The following table shows the relationship between the number of values and the margin it affects. The same syntax works for padding, as well.
| Shorthand notation | Margins set |
|---|---|
margin: 50px; |
top, right, bottom, and left margins set to 50 pixels. |
margin: 50px 20px; |
top, bottom margins set to 50 pixels. left, right margins set to 20 pixels. |
margin: 50px 20px 40px; |
top margin set to 50 pixels. left and right margins set to 20 pixels. bottom margin set to 40 pixels. |
margin: 50px 20px 40px 30px; |
top margin set to 50 pixels. right margin set to 20 pixels. bottom margin set to 40 pixels. left margin set to 30 pixels. |
/* CSS BEFORE MINIFICATION */
/* ********** Page-wide CSS styles ********* */
html {
background-color: #7BC143;
color: white;
font: 75% sans-serif;
text-align:center;
}
/* ********** HTML header element CSS styles ********* */
header{
color: gray;
font-size: 150%;
}
/* ********** Reference text CSS styles ********* */
.reftext {
font-size: 18px;
}
/* ********** Navigation bar related CSS styles ********* */
#navbar
{
background-color: #D61F57;
margin-left: auto;
margin-right: auto;
width: 50%;
}
#navbar a{
border-right: 1px solid;
color: inherit;
display:inline;
padding: 10px;
}
#navbar a:link {
color: white;
text-decoration: none;
}
The CSS code after minification is shown below.
/* CSS AFTER MINIFICATION */
html{background-color:#7BC143;color:#fff;font:75% sans-serif;text-align:center}header{color:gray;font-size:150%}.reftext{font-size:18px}#navbar{background-color:#D61F57;margin-left:auto;margin-right:auto;width:50%}#navbar a{border-right:1px solid;color:inherit;display:inline;padding:10px}#navbar a:link{color:#fff;text-decoration:none}
As seen above, the size of the CSS file has been drastically reduced. We used a CSS compressor with the highest available compression level. The input size was 698 bytes while the output CSS was 336 bytes, resulting in an overall compression of 107%. All the comments and spaces are removed and the readability of the code is compromised, but browsers have absolutely no problems reading a minified CSS file. Minification is encouraged just before we are ready to place the CSS file on the server.
-webkit (Chrome, Safari, and newer versions of Opera), -moz (Firefox), -ms (Internet Explorer), and -o (older versions of Opera).
A good example is the Flexbox property, which has undergone a few changes since its inception. For Firefox, the 2009 version used display: box;, the 2012 version used display: flexbox;, and the current version uses display: flex;. Now, IE10 only supports the 2012 version. To maximize support across different browsers, specific versions of these properties must be used along with their vendor prefixes. This requires much effort if we wish to do it manually.
Fortunately, tools, such as Autoprefixer, are available to us that automatically add vendor prefixes to our CSS code. These tools also remove prefixes that are not necessary and makes the CSS compatible with older browser versions. The best way to use Autoprefixer is by enabling it in CodePen. Once in CodePen, click the gear icon next to the CSS and select the option to enable Autoprefixer, as shown in the following image:
As an illustration, let's look at a CSS code for Flexbox:
/* Without Prefixes */
.topNavigation {
background: navy;
font-family: Georgia;
list-style: none;
/* Flexbox properties*/
display: flex;
justify-content: flex-end;
}
/* With Prefixes */
.topNavigation {
background: navy;
font-family: Georgia;
list-style: none;
/* Flexbox properties*/
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: justify;
-webkit-justify-content: flex-end;
-ms-flex-pack: justify;
justify-content: flex-end;
}
Autoprefixer, as the name suggests, will analyze the properties that have different versions of vendor prefixes and automatically add them to the existing property. This increases the size of the CSS code, but it ensures that the property renders evenly across all browsers.
html {
font-family: Georgia;
background-color: #ffe5ff;
margin-left:20px;
}
.subheading {
text-decoration: underline;
}
.beforeTranslate{
border: 2px solid black;
width: 500px;
height: 50px;
background-color: blue;
}
.afterTranslate{
border: 2px solid black;
background-color: blue;
color: white;
width: 500px;
height: 50px;
transform: translate(20px,40px); /* Standard syntax */
opacity: 0.7;
}
.beforeScale{
border: 2px solid black;
width: 100px;
height: 50px;
background-color: blue;
}
.afterScale{
width: 50px;
height: 50px;
border: 2px solid black;
transform: scale(4,2);
margin-left: 100px;
opacity: 0.7;
background-color: red;
}
.beforeSkewX, .beforeSkewY, .beforeSkewBoth, .beforeRotateClockwise, .beforeRotateCounterClockwise,.beforeCombined{
border: 2px solid black;
width: 150px;
height: 50px;
background-color: orange;
}
.afterSkewX{
border: 2px solid black;
width: 150px;
height: 50px;
background-color: orange;
opacity:0.7;
transform:skewX(35deg);
}
.afterSkewY{
border: 2px solid black;
width: 150px;
height: 50px;
background-color: orange;
opacity:0.7;
transform:skewY(25deg);
}
.afterSkewBoth{
border: 2px solid black;
width: 150px;
height: 50px;
background-color: orange;
opacity:0.7;
transform:skew(35deg, 25deg);
}
.afterRotateClockwise{
border: 2px solid black;
width: 150px;
height: 50px;
background-color: yellow;
transform:rotate(35deg);
opacity: 0.7;
}
.afterRotateCounterclockwise{
border: 2px solid black;
width: 150px;
height: 50px;
background-color: yellow;
transform:rotate(-1.57rad);
opacity: 0.7;
}
.afterCombined{
width: 150px;
height: 50px;
border: 2px solid black;
transform: scale(4,2);
margin-left: 100px;
opacity: 0.7;
background-color: red;
transform: translate(20px, 20px) rotate(30deg) scale(2,2);
}
.accordion{
transition: all .2s ease-out;
transition-delay: 1s;
}
.transform-box{
transform: translate(20px, 20px) rotate(30deg) scale(2,2);
box-sizing: border-box;
background: linear-gradient(orange, white);
border-radius: 5px;
}
block.block__element.block--modifier..block__element__element is not allowed as per BEM rules..navbar {}, .navbar__item {}, and .navbar--blue {}) would look like. Consider this HTML navigation bar snippet, which we created in a previous chapter:
The corresponding CSS structure includes the following CSS classes:
/* CSS */
.topNavigation {/* ... */ }
.topNavigation li {/* ... */ }
.topNavigation li a {/* ... */ }
The same code in BEM would be transformed into:
Now, according to BEM, the CSS classes would look like:
/* CSS */
.nav { /* ... */ }
.nav__item { /* ... */ }
.nav__link { /* ... */ }
.nav--blue { /* ... */ }
As we can see, the need for cascading CSS selectors is eliminated as each entity performs its own function, depending on its type (block, element, or modifier).
The following embedded CodePen contains all of the above code:
[codepen_embed height="500" theme_id="20476" slug_hash="73f60055a2d792c1c04d3731155cc9da" default_tab="result" user="moderndeveloper"]See the Pen BEM Illustration 1 by Modern Developer (@moderndeveloper) on CodePen.[/codepen_embed]
The outer ul element is the parent block (denoted by class="nav"), which is the entity that contains the navigation items, or elements in BEM terminology. The navigation items are indicated by the li items (denoted by class="nav__item").
Within the list items are the hyperlinks, which are, again, elements according to BEM. As previously mentioned as a word of caution, syntax like .nav__item__link is not allowed in BEM. Hence, we created a new entity with a CSS class of .nav__link to indicate a link within a navigation bar.
Finally, the .nav--blue is a modifier that is used to change the color of the navigation bar to blue.
.btn.
The button label (or text) is an active part of the button and cannot exist without its parent entity. Hence, we create a CSS class named .btn__text {} for the button text according to the rules of BEM. The HTML and the CSS code to create a simple button are shown below.
Standard Button
/* CSS */
/* Block */
.btn {...}
.btn__text {...}
To easily create variations of this button, we wrote a few modifiers. We made three types of buttons---big, medium, and small---and assigned them CSS classes of btn--big, btn--medium, and btn--small respectively.
Similarly, we also created three different button colors to choose from. Since the color modifies the appearance of the block entity (button), it is recognized as a modifier in BEM. The three CSS classes for creating red, green, and yellow buttons are .btn--red {}, .btn--green {}, and .btn--yellow {} respectively. This gives us the leverage to generate any combination of buttons by simply using a combination of classes we created.
For instance, to create a big, red button, we make use of the following HTML code:
Big Red Button
To make a medium-sized button appear green, the following code is used:
Medium Green Button
We have achieved several objectives in a single refactor of the CSS code using BEM.
class="btn btn--medium btn--green" self-describes the button being medium-sized and green in color.
This is a legitimate example, but it completely violates the naming convention recommended by BEM. The recommendation to use modifiers belonging to one block for a different block is not written in stone, but doing so does not describe what's going on with the HTML code. Utilizing modifiers for different blocks might give us the desired result, but it makes the code confusing for other developers.
html, body, a, a:link, etc. This includes CSS resets, which should be written at the start of your main CSS.
html {
font-family: 'Roboto';
}
body {
margin: 0;
padding: 0;
}
ul {
font-size: 12px;
}
h1 {
font-family: 'Courier';
}
CSS Reset is another good example of adding base styles. Its sole purpose is to remove browser inconsistencies in heights, margins, padding, font sizes by defaulting them to certain values. Since base rules use CSS selectors, SMACSS does not recommend any particular naming convention for this category.
#header, #footer {
width: 800px;
}
#footer {
background-color: #D9D9D9;
}
In cases where the higher-level layout must be tweaked (based on user preferences or the size of the viewport), we can add other layout styles. SMACSS recommends using either l- or layout- as prefixes to the selectors to indicate a layout rule.
.image {
float: left;
}
.image-description {
float: right;
}
.l-flipped .image {
float: right;
}
.l-flipped .image-description {
float: left;
}
In the example above, the .l-flipped class allows the position of the image and its description to be swapped. As we notice, using ID selectors is not mandatory, but if we ever decide to use them, they need to be specified in the Layout styles.
.contactme > p {
margin-left: 10px;
}
.contactme span {
text-decoration: underline;
}
This works fine when the span and p elements are styled exactly the same way every time the contact module is used. But if the requirements change over time, and we need to style multiple span elements differently within the module, element and descendant selectors will create a bottleneck. Let's have a look at how this could possibly happen.
Email
/* The Contact Me Module */
.contactme > span {
padding-left: 20px;
background: url(icon.png);
}
Everything works fine until we decide to add a telephone number. We do not want the same icon to show up for the telephone entry. Using element and descendant selectors has put us in trouble. The solution is to use selectors that describe the situation as clearly as possible. We can make use of CSS classes to remove any ambiguity and increase the semantics of the elements to be styled.
The revised markup looks like this:
Email
Telephone
/* The Contact Me Module */
.contactme-email {
padding-left: 20px;
background: url(email-icon.png);
}
.contactme-tel {
padding-left: 20px;
background: url(tel-icon.png);
}
The more generic the HTML selector is (such as div or span), the more likely there will be a conflict. But if we foresee the possibility of requirements changing, we should stick to the more semantic CSS classes.
is- as a prefix to the selectors to indicate a state-related rule.
Email
Telephone
The outer div element has an id attribute on it. This indicates the specified id is a layout related style (as previously discussed) and the is-hovered class represents a hover state on that div. The inner div has a span tag under it that has a message module hooked to it. We can easily attach an is-error state to it.
State changes are represented in one of three ways:
:active, :hover, and :focus. CSS also contains structure related pseudo-classes, like :first-of-type, :nth-child(n), and :nth-of-type(n), etc. In addition to these, a number of other UI-related pseudo-classes respond to interactions in form handling.
The default state does not come with a pseudo-class. We can define styles for pseudo-classes as a secondary state of the module.
.button{
background-color: #444;
}
.button:hover{
background-color: #448;
}
.button:focus{
background-color: #44B;
}
If variations (or sub-modules in SMACSS terminology) of the button exist, we need to design pseudo-class states for these sub-modules as well.
.button{
background-color: #444;
}
.button:hover{
background-color: #448;
}
.button:focus{
background-color: #44B;
}
/* Warning button*/
.button-warning{
background-color: #F22;
}
.button-warning:hover{
background-color: #F44;
}
.button-warning:focus{
background-color: #F66;
}
/* Accordion-Style Rules Start */
.accordion {
margin: 10px;
background-color: #000000;
...
}
.is-accordion-collapsed{
background-color: #FFFFFF;
}
/* Accordion-Style Rules End */
Although the name of the state class is a little longer, it is very descriptive and fully qualifies the collapsed state of the accordion.
message module we created above needs to have the font in 10 point size. The default font specifications would be initially specified in the module, while the new size would be specified in the theme.
// in modules.css
.message {
font-family: 'Verdana';
}
// in theme.css
.mod {
font-size: 10pt;
}
#article div {
border: 1px solid;
background-color: gray;
}
#article div h4 {
text-decoration: underline;
}
#article div ul {
padding: 2px;
}
Looking at the CSS gives us a fairly good idea about the HTML structure; the element with id="article" is a container that has more than one section, having a heading and an unordered list. We can observe that the flow of our CSS selectors relies heavily on the HTML structure.
This approach would work fine for a smaller website and one which does not undergo frequent layout changes. But for larger websites having varied code requirements, the HTML structure is bound to undergo changes. This forces the developers to add more selectors, which in turn, adds more rules.
The second and the more important observation is the depth of HTML to which the selectors are applied. Depth of Applicability is the number of generations affected by a rule. For instance, the depth of applicability in #article > div > h4 is the generations. The greater the depth of the HTML selectors, the higher the coupling between CSS and HTML.
Let's look at another example to understand the depth of applicability. The following snippet shows the HTML code for a simple navigation bar:
The longest selector that can be written in CSS is #navbar ul li a which impacts four generations. Even if the selector is written as #navbar a, its depth is still four generations. The greater the depth, the greater the dependency on the underlying HTML structure. This is what strongly couples HTML and CSS, and it is what we would like to avoid. As a result, page components can't be moved and more duplication of code is possible.
Ultimately, each developer must decide to adopt as many of the best practices we've discussed here as possible. Some of them are optional (such as using BEM instead of SMACSS), and some of them need refining (CSS Reset script). Adopting them from the start of the project ensures that the CSS code evolves in a clean, concise manner. As Nicholas Gallagher rightly summarizes in his blog:
Anyone can rearrange pre-built "lego blocks"; it turns out that no one can perform CSS-alchemy.
Well-maintained CSS (or HTML or JavaScript, for that matter) is evident when developers can confidently modify parts of the code while adhering to a commonly agreed standard and not break any other part of the project. The only way to appreciate these best practices is to put them in practice during our coding routine.
This concludes the discussion of some of the commonly adopted best practices in CSS. Let's apply this knowledge to a couple of scenarios in order to make our CSS code clean and easy to read.