This resource contains a collection of best CSS practices and CSS tips provided by our Toptal network members. As such, this page will be updated on a regular basis to include additional information and cover emerging CSS techniques. This is a community driven project, so you are encouraged to contribute as well, and we are counting on your feedback.
Cascading Style Sheets (CSS) can be described as a style sheet language written in a markup language. It is used for defining the look and formatting elements of Web documents such as the layout, colors, and fonts. The CSS specifications are maintained by the World Wide Web Consortium (W3C). Even though every browser supports CSS, there are many inconsistencies in the supported specification version. Some browsers even have their own implementation of the specification and have proprietary (vendor) prefixes. Supporting all modern browsers is a daunting task, not to mention when developers need to support old and legacy browsers. All these problems cause a lot of trouble for developers, and it is hard for them to write CSS code that will render consistently across all browsers. That’s why we want our top CSS developers and engineersto share their knowledge and offer their best tips to achieve those tasks more easily.
How to Improve CSS Performance
There is a lot you can do to ensure good CSS performance and reduce page loading times. Here are some tips and techniques you can start implementing today.
A browser needs to go through every single DOM element to find what it is looking for. Take this example:
.home-page .header-main .nav-main ul
The browser will begin by looking for every ul
element, then every .nav-main
inside of a ul
, then every .header-main
inside of that, and then every .home-page
inside of that. Only when the search is done are styles applied. We can be far more efficient than that and write a specific selector, like this:
.nav-list
Now the browser simply needs to find the .nav-list
element and apply the styles to it. We have kept specificity low and avoided unnecessary nesting. Simple, yet efficient.
Naming classes can be tough, but you can use some of the well known tricks for that. For instance, in our .nav-list
element, you can use the .component-descendant-descendant
naming:
.nav-list-item
The .nav-list-item
would be the list item in the navigation. Alternatively, you can use the BEM naming methodology, already covered in our CSS tips.
Another piece of advice for keeping low specificity is to avoid IDs and use classes. The big benefit of using classes is that you can reuse your styles and help keep your style sheets DRY (Don’t Repeat Yourself), which will also reduce the file size.
In the end, you can minimize the CSS to make it load faster.
How to Write Modular CSS Code
Code duplication is one of the most important issues that originate from coding in plain CSS. In order to contest that, preprocessors come with very strong function, mixin or extend implementations. In this tip we are going to introduce @include
and @extend
, and discuss how they can be used efficiently in SCSS.
Creating Mixins with @include and @mixin
By utilizing the @include
implementation, you can create mixin functions with parameters. For example, a display block centered container with max-width could be implemented as follows:
// Create a max-width container
@mixin maxWidthContainer($width : 1024px) {
display: block;
max-width: $width;
margin: 0 auto;
}
The mixin declared above can be called with the following command: @include maxWidthContainer();
. Since the default value for a $width
parameter is set to 1024px, generated CSS will look like this:
display: block;
max-width: 1024px;
margin: 0 auto;
Extending Classes with @extend
The @extend
can be used to share CSS properties from one class to another. For example, the following button style declarations in SCSS:
.button {
color: black;
background: white;
border: 1px solid gray;
}
.button--warning {
@extend .button;
color: red;
background: orange;
}
.button--disabled {
@extend .button;
color: white;
background: gray;
}
Will result in the following CSS:
.button, .button--warning, .button--disabled {
color: black;
background: white;
border: 1px solid gray;
}
.button--warning {
color: red;
background: orange;
}
.button--disabled {
color: white;
background: gray;
}
Please notice how the button modifiers (.button--warning
and .button--disabled
) are stacked in the initial declaration of .button
.
Efficient Usage of @include and @extend
Mixin declaration is the most useful function that preprocessors have to offer. @include
can be used for anything from adding animations to responsive breakpoints. Any piece of code that is repeated multiple times should be applied into a mixin which will effectively make your code more readable and easily maintainable.
On the other hand, @extend
has to be used very carefully and sparsely. While initially it seems to pose plenty of advantages, in its use there are a few disadvantages:
- You can not extend from within a media query
- It does not accept any parameters
Both @include
and @extend
are very strong directives which can make CSS development with the aid of preprocessors way more productive as well as fun.
Using Stylus to Easily Decouple Semantic Markup from CSS Stylesheets.
Writing semantic markup helps to separate content from style, and it makes it easier to generate HTML documents that will not change and will not be updated with every little change in the CSS files. Keeping developers’ minds wrapped around that concept can be tedious when vanilla CSS is used. Usually, front-end developers are cutting corners by using classes like float-left
, clear
, column
, grid-3
, etc. This approach might get the job done faster initially, but any subsequent change to the design will require a significant update to the HTML files as well. That way, the codebase easily becomes very hard to maintain, hard to read, and hard to understand, especially if someone inherits such code.
Stylus, with its powerful features like mixins, placeholders, named parameters, and interpolation, can help to decouple semantic markup. We will show it by an example. In Stylus we can combine the concept of placeholders, mixins, and named arguments to implement a very simple and customizable responsive grid system. Full code example can be found in this codepen
Let’s take a closer look at the grid-fn
function and the grid
mixin.
Setting the default variables basically sets up our grid layout. It’s fully flexible, and we can adjust it to our design needs. The grid-fn
function calculates the column width and outputs a value based on number of columns and margins we want something to span. The grid
mixin uses the grid-fn
function to set a property value and enables us to play with the responsiveness of elements we style. The simplest way to use it would be as follows:
article
grid width 6
By using named parameters, we can get really expressive:
article
grid(when:below, breakpoint:800px, width, 4)
We can even use this responsive definition as a block mixin call and nest selectors or properties underneath it which will be applied if that particular media query gets matched:
article
+grid(when:below, breakpoint:800px, width, 4)
h1
font-size 1em
Now we can use this neat mixin to generate a set of placeholders to use as needed. Placeholders basically work like classes. They are defined and used to extend concrete selectors, but only show up in the generated CSS if they are used in our stylus file. This is good because we can be sure the CSS file won’t include any unnecessary bloat that’s not being used at all:
$column-base
padding 0
margin-bottom grid-margin
&:first-child
margin-left 0
&:last-child
margin-right 0
for col in (1..grid-columns)
$column{col}
@extends $column-base
float left
grid width col
margin-left (grid-margin / 2)
margin-right (grid-margin / 2)
By using the interpolation operator {}
, we can programmatically define our placeholders and extend them to make them more readable and flexible:
$columns-full
@extends $column{grid-columns}
$columns-half
@extends $column{grid-columns / 2}
We can now use this logic to completely remove any layout-related classes from our markup, and simply use the placeholders to describe HTML elements:
article
@extends $columns-full
main
@extends $columns-two-thirds
aside
@extends $columns-third
You can notice that even if we modify our variables later on, placeholders like $columns-full
or $columns-half
will still work as expected.
This is one example of how preprocessor features can serve as excellent tools for writing and maintaining a sane markup and stylesheet structure. Getting familiar and comfortable with the tools we use is a great way to boost developer productivity and code quality.
How Can CSS Namespacing Aid Front-End Development?
Namespacing can be used in any type of programming language, and the advantages are innumerable. However with CSS, some of the advantages are not particularly obvious. We will take a look at the two most common namespacing usages in CSS and we will discuss their advantages in more detail.
Variables
Using variables in CSS is the simplest and the most common use. Let’s take a look at the following SCSS example code:
// Our common variables
// Fonts
$f_arial: Arial, Helvetica, sans-serif
// Colors
$c_red: #FF0000;
$c_black: #000;
// Z-indices
$z_index_back: -1;
$z_index_base: 1;
$z_index_max: 9999;
// Animation Timings
$t_medium_animation: 200ms;
// Breakpoints
$b_mobile: 700px;
$b_desktop: 1200px;
In the example above, we have defined five different organizational categories:
$f_
font and webfont families$c_
color codes$z_
z-indices used throughout the application$t_
animation timings$b_
responsive breakpoints
The biggest advantages of utilizing the organizational categories structure with variables as described above are code clarity and rapid development. This is especially important in large teams where more developers work on the same CSS files. One more notable advantage is added autocomplete feature. Simply by entering a $f_
in our IDE we will get an autocomplete list of all declared fonts, or by entering a $b_
we will get all responsive breakpoints, and so on.
Class Names
The second most common usage of namespacing is by utilizing a CSS class naming. The prefix o-
can be used for CSS object class name declarations, while c-
can be used for classes. The fundamental distinction between objects and classes is that they may appear more than once in different templates of our design, hence making them harder to meddle with. Separating objects from classes makes it easier for a developer to navigate within a relatively unknown codebase. Another clever concept is to disassociate all the classes solely used in JavaScript with the js-
prefix. Finally, is-
can define application states such as is-active
or is-visible
, while u-
can prefix utility classes such as u-pull-left
.
How to write maintainable CSS declarations
Why is the order of the CSS declarations important? Let’s cover the basics first.
A CSS declaration is made up of a selector, or a group of selectors, and a declaration block. Inside of the declaration block, there are declarations with properties and values.
The order of those declarations may seem unimportant at first glance, but defining an order does hold some advantages. An obvious advantage, which is especially important in larger teams, is consistency. Some developers write declarations randomly, some alphabetically, and some group them by type. Grouping is considered to be a good choice because it makes sense to write the positioning-related declarations first as some elements could be removed from the flow. The browser digests it first, and the developer can read the block with ease knowing where the element will be positioned right away. Like the Bootstrap creator Mark Otto, Toptal CSS developers have found the following order of styles easy to maintain:
- Positioning
- Box model
- Typographic
- Visual
Here is a general example:
.declaration {
/* Positioning */
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 100;
/* Box-model */
display: block;
float: right;
width: 100px;
height: 100px;
/* Typography */
font: normal 16px Arial, sans-serif;
line-height: 1.5;
color: #333;
text-align: center;
/* Visual */
background-color: #fff;
border: 1px solid #eee;
border-radius: 5px;
}
For developers using a CSS preprocessor, the best practice is to write @extend
rules first and @include
rules second. The reason for that is the fact that you’re aware right away that those styles are inserted into the selector, and you are able to easily override them below it.
How to customize an HTML file input?
Nowadays, every web project is expected to look appealing and beautiful. To achieve this, web developers use a lot of CSS and JavaScript to generate beautiful and useful user interfaces.
Sometimes getting a small element to look nice and consistent across browsers and platforms looks more challenging than it really is. Let’s imagine that you want to create a custom file input that will look like the one below:
When you type <input type=”file” />
in your HTML file, you will be quickly disappointed with the default result that will look something this:
You will not be able to achieve the result you want if you try to style the file input directly. Each web browser renders the file inputs its own way, and no two browsers will display it in the same way. Not to mention that the default browser rendering looks unattractive. There are many ways to render an HTML file input so it looks and behaves consistently across all browsers. There are even plugins to help you get the job done. But our best CSS Toptal developers have a simple and elegant way to customize file inputs using just CSS and a little touch of JavaScript help.
The first thing you need to do is create the HTML markup by putting the file input into another tag. We will use a span tag in this example, but you can use any tag you want:
<span class="file-input">
Browse<input type="file" />
</span>
The result of the code above is as follows:
Now we are going to hide the file input but keep it clickable. When users click on the Browse
text, they will actually be clicking on the hidden file input. To do so, we have to add the CSS below:
.file-input {
position: relative;
overflow: hidden;
}
.file-input input[type="file"] {
position: absolute;
top: 0;
right: 0;
min-width: 100%;
min-height: 100%;
text-align: right;
filter: alpha(opacity=0);
opacity: 0;
outline: none;
display: block;
cursor: pointer;
}
What the code above does:
- The
span
.file-input
is positioned relatively so its children could be positioned absolutely if we want. - The file input is positioned absolutely to stay over the
Browse
text. We also set its opacity to 0 so it won’t be visible. The cursor: pointer will show the users that they can click it.
After applying this CSS, the result will be:
You can notice that the Browse
text is clickable and dispatches the file search, as we want.
Now we will just add more style to the .file-input
class so it will turn into a green button:
.file-input {
position: relative;
overflow: hidden;
border: 1px solid #1f7c57;
border-radius: 3px;
background: linear-gradient(#43c692, #39b885);
color: white;
padding: 5px 10px;
display: inline-block;
font-family: 'Trebuchet MS';
font-weight: bold;
}
The next result is:
Great. It looks like we are done. But we have a little problem: once we click on the button and select a file, no result is shown to the user. This is because the file input is hidden, so the users can’t see the file name that is selected. A little JavaScript function will help us to solve this problem. We will add an empty tag <span class="filename"></span>
and use it to show the file name once the file is selected.
By adding the style below to the span
.filename
, it will make it prettier:
.filename {
font-family: 'Trebuchet MS';
padding: 5px 10px;
display: inline-block;
vertical-align: top;
font-style: italic;
}
Now we are going to add a function that listens to the file input changes and fills out the span
.filename
. The example below is using jQuery, but we can also do it by just using a pure vanilla JavaScript:
<script src="//code.jquery.com/jquery-1.11.3.min.js"></script>
<script type="text/javascript">
$(document).ready( function() {
$('.file-input input[type="file"]').change( function() {
var filename = $(this).val().replace(/\\/g, '/').replace(/.*\//, '');
$('.filename').html( filename );
});
});
</script>
Now our code is ready. When a user selects a file, the file name will be placed into the span
.filename
.
You can download the example code on GitHub.
What is a Block, Element, Modifier Approach?
When building complex user interfaces using CSS, it is mandatory to have defined code writing guidelines and implementation approaches in place. There are plenty of online resources with suggestions on how to structure the code, and one of the most commonly embraced naming conventions is “Block, Element, Modifier”, or BEM for short.
This approach is based on three building blocks, which can be combined to describe even the most complex user interfaces. The following example shows how a button element with an enclosing text container can easily be described using a BEM, written in SCSS:
// A simple button
// .o-button--red changes the button color to red
$m : 'o-button';
.#{$m} {
background: white;
&__text {
color: black;
}
&--red {
background: red;
.#{$m}__text {
color: white;
}
}
}
When compiled, the above SCSS is transpiled to the following CSS code:
.o-button {
background: white;
}
.o-button__text {
color: black;
}
.o-button--red {
background: red;
}
.o-button--red .o-button__text {
color: white;
}
In the above code, a block is a top-level element, a parent that is meaningful on its own, and in our example this is a button .o-button
. Child elements are semantically tied to its block, and they only make sense in the context of the block they belong to. They are denoted by two underscores following the name of the block, in our example .o-button__text
. Finally, modifiers change appearance or behavior of the block. They are denoted by two hyphens to the name of the block, in our example .o-button--red
.
To summarize, the BEM naming convention is as follows:
.block {}
.block__element {}
.block--modifier {}
The HTML example below utilizes the previously created styles:
<p>This is a normal button</p>
<button class="o-button">
<span class="o-button__text">Click</span>
</button>
<p>This is a button modified to be red</p>
<button class="o-button o-button--red">
<span class="o-button__text">Click</span>
</button>
From the compiled CSS you may notice that specificity is kept to a bare minimum. Additionally, code reusability is encouraged by adding modifiers to describe the design variations of each element. Finally, BEM’s comprehensive class names make it a great methodology to be used when collaborating with other developers.
How to write consistent CSS?
Whether you are a one-man-band or a member of a team, all CSS should look like a single person was writing it, even when there were many developers contributing to the code. This consistency in CSS can be achieved through a set of guidelines, and following these guidelines will result in clean code that is easy to maintain.
How do you write these guidelines? The best Toptal CSS developers are here to help you. At the start of a project, write a project guideline and make sure all developers are on board and following them.
Here are some guidelines you can define:
- the declaration order inside of a property
- the declaration’s format – some developers prefer single line formatting
- indentation – using tabs or soft-tabs
- what kind of quotes will be used
- will you use spaces after colons, before and after brackets, and so on
- will you use trailing semi-colons or not
- will you prefix values with a leading zero
- will you use lowercase or uppercase for the hexadecimal values
- will you use the shorthand properties
- the way nesting will be used
- will you use a CSS architecture, like SMACSS
- will you use a naming methodology, like BEM
The level of details you’ll go into is up to you. I’d suggest you decide which of those are important and which are not, because having too many strict rules could slow your team members down. When you define the rules, make sure that the team members are following them. The result will be code that is clean and maintainable. Of course, using a preprocessor like LESS or SASS helps to get clean code, but you still need to have a clean source code so all team members won’t have a hard time using it.
This article was originally published on Toptal
Comments are closed, but trackbacks and pingbacks are open.