I went to the CSSConf.asia in Singapore recently and enjoyed lots of interesting talks. Out of all interesting talks that are given out that day, the information that stands out the most to me was about writing maintainable CSS and creating improving the performance of the UI.
I work with a team of both front-end and back-end engineers. Therefore, writing maintainable code is very important to keep the productivity up and make everyone happy. Of course, it is important to focus on performance as well in order to keep your users happy; no one loves a slow and janky site.
The conference inspires me to start blogging again and work on some of unfinished projects. Honestly, I have been enjoying life too much lately.
Lets not start with some of my projects that I have been ignoring lately. There are feature requests, emails, and pull requests I still need to entertain. *sigh*
Old habits die hard
Prior to working with a team at Viki, I never really focus on making my CSS maintainable in my personal projects. I just care about making things work and ensuring that it looks pretty.
When I post CSS play things on CodePen, I always have the mentality that my code is not supposed to be production ready; it is just supposed to be pretty. I am also probably influenced by some of the pretty but not practical examples that I saw on CodePen. As a result, I have pretty demos but ugly CSS. Hence, my undying love towards my pretty ugly CSS code.
This is not a good thing because it started to become a habit of mine to write ugly CSS. I would continue writing ugly stuff on my own personal projects. This change when I started working with a group of people.
Working with a team has certainly change the way I write CSS code. At Viki, our team work with Sass with Compass all the time. When working with Sass, there are several guidelines that you need to keep in mind in order to prevent CSS code bloat, shitty performance, and nesting hell.
Measuring Improvement: Picking on myself
Have I improved? Why, yes! How do I know? Taking a look at one of my old pens, it is quite satisfying to understand why my CSS code was bad and knowing the right way to fix it.
I was looking at one of my old pens and I become highly critical of the code that I had written. Lets pick on myself by taking a look at the snippets of my HTML and CSS code from my Pure CSS Horizontal Accordion.
Note: The ugly and old version of the code is commented out.
/*
The commented version out this Sass code is ugly and is replaced by a better one. Scroll down for the production ready version. Enjoy!
@import "compass/reset";
@import "compass/css3";
$body: #f77462;
$list: #6dc5dd;
$border: #5ab2ca;
$geneicons: #fff;
body {
background: $body;
font-family: Lato, sans-serif;
}
@font-face {
font-family: 'Genericons';
src: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/53819/genericons-regular-webfont.eot');
src: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/53819/genericons-regular-webfont.woff') format('woff'),
url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/53819/genericons-regular-webfont.eot') format('truetype');
font-weight: normal;
font-style: normal;
}
[class*="genericon"] {
display: inline-block;
width: 16px;
height: 16px;
-webkit-font-smoothing: antialiased;
font-size: 16px;
line-height: 1;
font-family: 'Genericons';
text-decoration: inherit;
font-weight: normal;
font-style: normal;
vertical-align: top;
}
[class*="genericon"] {
*overflow: auto;
*zoom: 1;
*display: inline;
}
.wrap {
margin: 50px auto;
width: 100%;
}
.tag > a:first-child:before,
.tag > a:nth-child(2):before {
position: absolute;
top: 90px;
left: 25px;
display: block;
vertical-align: text-bottom;
font: normal 1.5em Genericons;
color: $geneicons;
padding-right: 4px;
}
.tag > a:nth-child(2):before {
font-size: 3em;
left: 20px;
top: 75px;
}
.tag > a:first-child,
.tag > a:nth-child(2) {
position: absolute;
width: 85px;
height: 200px;
@include transition( all 0.4s 0.1s ease-out )
}
.tag > a:first-child {
background-color: $list;
}
.tag > a:nth-child(2) {
margin-left: 85px;
background: #eee;
}
#github > div.tag > a:first-child:before,
#github > div.tag > a:nth-child(2):before {
content: '\f200';
}
#twitter > div.tag > a:first-child:before,
#twitter > div.tag > a:nth-child(2):before {
content: '\f202';
}
#facebook > div.tag > a:first-child:before,
#facebook > div.tag > a:nth-child(2):before {
content: '\f204';
}
#linkedin > div.tag > a:first-child:before,
#linkedin > div.tag > a:nth-child(2):before {
content: '\f208';
}
#insta > div.tag > a:first-child:before,
#insta > div.tag > a:nth-child(2):before {
content: '\f215';
}
#youtube > div.tag > a:first-child:before,
#youtube > div.tag > a:nth-child(2):before {
content: '\f213';
}
#tumblr > div.tag > a:first-child:before,
#tumblr > div.tag > a:nth-child(2):before {
content: '\f214';
}
#dribbble > div.tag > a:first-child:before,
#dribbble > div.tag > a:nth-child(2):before {
content: '\f201';
padding-top: 3px;
margin-top: -3px;
}
#github > div.tag > a:nth-child(2):before {
color: #333;
}
#github > div.tag > a:nth-child(2) {
background: #e6e6e6;
}
#twitter > div.tag > a:nth-child(2) {
@include background-image(linear-gradient(#7adcf9, #4bc9f5));
}
#facebook > div.tag > a:nth-child(2) {
@include background-image(linear-gradient(#548abf, #295b9e));
}
#linkedin > div.tag > a:nth-child(2) {
@include background-image(linear-gradient(#00a9cd, #0083b4));
}
#youtube > div.tag > a:nth-child(2) {
@include background-image(linear-gradient(#df192a, #c41222));
}
#insta > div.tag > a:nth-child(2) {
@include background-image(linear-gradient(#7fc121, #298733));
}
#tumblr > div.tag > a:nth-child(2) {
@include background-image(linear-gradient(#283e56, #325372));
}
#dribbble > div.tag > a:nth-child(2) {
@include background-image(linear-gradient(#e03a70, #f189b8));
}
.accordion {
background: $border;
position: fixed;
width: 100%;
height: 200px;
margin-top: -100px;
left: 0;
top: 50%;
overflow: hidden;
ul {
margin-left: 30px;
list-style-type: none;
}
li {
overflow: hidden;
position: relative;
background-color: $list;
border-right: $border 1px solid;
width: 80px;
height: 200px;
float: left;
display: block;
@include transition( all 0.4s 0.1s ease-out );
&:hover {
width: 450px;
a:first-child {
margin-left: -100px;
}
a:nth-child(2) {
margin-left: -5px;
}
}
}
.paragraph {
position: relative;
width: 360px;
margin-left: 80px;
padding: 50px 0 0 10px;
height: 200px;
background: #fff;
h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
p {
font-size: 0.88em;
line-height: 1.5em;
padding-right: 30px;
}
}
}
.clearfix {
zoom: 1;
}
.clearfix:before, .clearfix:after {
content: "";
display: table;
}
.clearfix:after {
clear: both;
} */
/* Updated version - 24th Nov 2014 */
@import "compass/reset";
@import "compass/css3";
/* Variables */
$backgroundColor: #f77462;
$list: #6dc5dd;
$border: #5ab2ca;
$geneicons: #fff;
body {
background: $backgroundColor;
font-family: Lato, sans-serif;
}
@font-face {
font-family: 'Genericons';
src: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/53819/genericons-regular-webfont.eot');
src: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/53819/genericons-regular-webfont.woff') format('woff'),
url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/53819/genericons-regular-webfont.eot') format('truetype');
font-weight: normal;
font-style: normal;
}
[class*="genericon"] {
display: inline-block;
width: 16px;
height: 16px;
-webkit-font-smoothing: antialiased;
font-size: 16px;
line-height: 1;
font-family: 'Genericons';
text-decoration: inherit;
font-weight: normal;
font-style: normal;
vertical-align: top;
}
/* IE7 */
[class*="genericon"] {
*overflow: auto;
*zoom: 1;
*display: inline;
}
.container {
margin: 100px auto;
}
.accordion {
background: $border;
width: 100%;
min-width: 950px;
display: block;
list-style-type: none;
overflow: hidden;
height: 200px;
font-size: 0;
}
.tabs {
display: inline-block;
background-color: $list;
border-right: $border 1px solid;
width: 80px;
height: 200px;
overflow: hidden;
position: relative;
margin: 0;
font-size: 16px;
@include transition( all 0.4s 0.1s ease-in-out );
&:hover {
width: 450px;
.social-links {
a {
&:before {
margin-left: -100px;
}
&:after {
margin-left: -5px;
}
}
}
}
.paragraph {
position: relative;
width: 360px;
margin-left: 80px;
padding: 50px 0 0 10px;
height: 200px;
background: #fff;
h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
p {
font-size: 0.88em;
line-height: 1.5em;
padding-right: 30px;
}
}
}
.social-links {
display: block;
a {
display: block;
text-indent: -9999px;
font-size: 0;
line-height: 0;
&:before, &:after {
@include transition( all 0.4s 0.1s ease-in-out );
width: 80px;
height: 200px;
position: absolute;
text-indent: 0;
padding-top: 90px;
padding-left: 25px;
display: block;
font: normal 30px Genericons;
color: $geneicons;
}
&:after {
font-size: 48px;
padding-left: 20px;
padding-top: 80px;
margin-left: 85px;
}
}
}
.twitter-icon {
a {
&:before, &:after {
content: '\f202';
}
&:after {
@include background-image(linear-gradient(#7adcf9, #4bc9f5));
}
}
}
.facebook-icon {
a {
&:before, &:after {
content: '\f204';
}
&:after {
@include background-image(linear-gradient(#548abf, #295b9e));
}
}
}
.linkedin-icon {
a {
&:before, &:after {
content: '\f208';
}
&:after {
@include background-image(linear-gradient(#00a9cd, #0083b4));
}
}
}
.insta-icon {
a {
&:before, &:after {
content: '\f215';
}
&:after {
@include background-image(linear-gradient(#7fc121, #298733));
}
}
}
.youtube-icon {
a {
&:before, &:after {
content: '\f213';
}
&:after {
@include background-image(linear-gradient(#df192a, #c41222));
}
}
}
.tumblr-icon {
a {
&:before, &:after {
content: '\f214';
}
&:after {
@include background-image(linear-gradient(#283e56, #325372));
}
}
}
.dribbble-icon {
a {
&:before, &:after {
content: '\f201';
}
&:after {
@include background-image(linear-gradient(#e03a70, #f189b8));
}
}
}
See the Pen Pure CSS Horizontal Accordion by Ren Aysha (@rrenula) on CodePen.
Uh, no.. just no. From the code, you would see crazy things like:
- HTML: Repetitive a href tags without value. Markup can be made much simpler.
- SASS: Coding it like I am still coding CSS. What’s up with the flood of ‘>’ symbol?
- HTML: Usage of IDs; not that IDs are bad but it can be unmaintainable if used unwisely. And when it comes to this code, everything is unwise.
- SASS: The use of floats; C’mon. We all know floats are bloody annoying. Inline-block is more likely the way to do it.
- SASS: Too much nth-child usage when it can definitely be made simpler if the markup is simpler. Pseudo classes of BOTH before and after can be used instead of a combination of nth-child and :before pseudo class.
- SASS: Usage of selectors like div.tags, which can make it hard to overwrite a particular property.
How to fix this ugly.. thing?
Lets make the markup simpler shall we? Instead of having the original ugly beast, we can definitely make it simpler like so:
<!-- Instead of having duplicated link tags -->
<li id="twitter">
<div class="tag">
<a href="http://twitter.com/renettarenula"></a>
<a href="http://twitter.com/renettarenula"></a>
</div>
<div class="paragraph">
<h1>Twitter</h1>
<p>My thoughts in 140 characters or less. Sometimes, I do not know how to correctly use Twitter.</p>
</div>
</li>
<!-- Make it simpler like this -->
<li class="tabs">
<div class="social-links facebook-icon">
<a href="http://facebook.com">Facebook</a>
</div>
<div class="paragraph">
<h1>Facebook</h1>
<p>Where I get to stalk my friends and let them stalk me. A place to get people to stroke your ego.</p>
</div>
</li>
Repetitive link tags are gone and we also have a proper text link. Instead of using IDs, we are using classes in order to differentiate between the social icons.
With that out of the way, we can focus on Sass. We are including a text link, which I didn’t do previously because I want an easy way out. But we don’t want this text link to be visible; we want the social icons to show instead of a text link. Therefore, we are going use a combination of the text-indent, line-height, and font-size to hide it.
.social-links {
display: block;
a {
display: block;
text-indent: -9999px;
font-size: 0;
line-height: 0;
}
}
Time to style each of the tabs! Instead of relying on floats to ensure that the elements are all inline side by side, we can switch that with inline-block property to minimize the headaches.
Also, I prefer to minimize unnecessary nesting as much as possible since it leads to overly specific selectors and also bad performance. I use nesting in order to style a particular component where the elements are connected as a group in order make up one specific component (think navigation or a carousel).
My personal preference is to get straight to the point by styling a specific selector that I want. The general advice is to not go more than 4 levels deep.
/* Instead of using floats and nesting */
.accordion {
background: $border;
position: fixed;
width: 100%;
height: 200px;
margin-top: -100px;
left: 0;
top: 50%;
overflow: hidden;
li {
overflow: hidden;
position: relative;
background-color: $list;
border-right: $border 1px solid;
width: 80px;
height: 200px;
float: left;
display: block;
@include transition( all 0.4s 0.1s ease-out );
}
}
/* We can use inline-block */
.accordion {
background: $border;
width: 100%;
min-width: 950px;
display: block;
list-style-type: none;
overflow: hidden;
height: 200px;
font-size: 0; // remove spacing between items
}
.tabs {
display: inline-block;
background-color: $list;
border-right: $border 1px solid;
width: 80px;
height: 200px;
overflow: hidden;
position: relative;
margin: 0;
font-size: 16px; // restore font-size back to normal
@include transition( all 0.4s 0.1s ease-in-out );
}
But as you know, there is a caveat to using inline-blocks: it adds unwanted spacing between the elements. My favorite solution to this is to use the font-size hack where you set the font-size to 0 for the parent in order to remove spacing and just specify a font-size on the inline-block element in order to restore the text.
Now, the social icons. My god, the original code looks like the mother of madness! Why the hell did I put two bloody link tags with :before pseudo-elements instead of using BOTH :before and :after pseudo elements?! I am sure I was high when I wrote this.
/* Instead of this mother of madness */
.tag > a:first-child:before,
.tag > a:nth-child(2):before {
position: absolute;
top: 90px;
left: 25px;
display: block;
vertical-align: text-bottom;
font: normal 1.5em Genericons;
color: $geneicons;
padding-right: 4px;
}
.tag > a:nth-child(2):before {
font-size: 3em;
left: 20px;
top: 75px;
}
/* A short and sweeter version */
.social-links {
display: block;
a {
display: block;
text-indent: -9999px;
font-size: 0;
line-height: 0;
&:before, &:after {
@include transition( all 0.4s 0.1s ease-in-out );
width: 80px;
height: 200px;
position: absolute;
text-indent: 0;
padding-top: 90px;
padding-left: 25px;
display: block;
font: normal 30px Genericons;
color: $geneicons;
}
&:after {
font-size: 48px;
padding-left: 20px;
padding-top: 80px;
margin-left: 85px;
}
}
}
Finally, setting up individual icons and background color for the tabs when hovered:
/* Instead of this code */
#twitter > div.tag > a:first-child:before,
#twitter > div.tag > a:nth-child(2):before {
content: '\f202';
}
#twitter > div.tag > a:nth-child(2) {
@include background-image(linear-gradient(#7adcf9, #4bc9f5));
}
/* A cleaner way (with better performance) */
.twitter-icon {
a {
&:before, &:after {
content: '\f202';
}
&:after {
@include background-image(linear-gradient(#7adcf9, #4bc9f5));
}
}
}
Note: The pseudo class is supposed to appear as &:before. For some reason, my syntax highlighter is adding an unwanted semi-colon.
Now, I am happy to say that the horizontal accordion code is production ready. I won’t feel guilty if someone copy pasted the entire thing and put it on their project.
Well, this has been fun. Feel free to try this out when you’re bored. Browse an old code and see what you can find – you may surprise yourself. I know I did. I am still in denial; I am trying to convince myself that I was either very, very high or very, very sleepy when I wrote this.