We will cover topics ranging from naming selectors, organizing your code to specificity and which units to pick. I divided this guide into 5 habits that are important for every developer learning or improving their CSS.
Table of Contents
- Habit #1: Consistent naming
- Habit #2: Use CSS variables
- Habit #3: Comment and organize
- Habit #4: Always use classes
- Habit #5: Use appropriate unit
Habit #1: Consistent naming
Can you look at the following selectors in CSS, and answer these 3 questions:
- WHAT do they do?
- WHERE can they be used?
- and are they RELATED to each other?
We know that .title
class is used to style the title, but, it doesn't tell us whether it's the main title of the website or the title of some article. And, we don't know if it's related to .card
element at all.
And, it's same issue with the other classes, too. Without looking at the HTML code, we can't answer those 3 questions.
Just like other programming languages, CSS has adopted its own naming rules that developers should follow.
These include the following:
- Names should be written in lowercase
- Separate the words with hyphens (-)
- Don't use camelCase, under_score or other naming conventions.
- And, here's my personal take: don't use abbreviations unless they're commonly used among developers. like
bg
forbackground
.
And, still, these are not enough to answer those 3 questions, especially when your project starts to grow.
Then, what is that secret sauce?
It's BEM, the most popular naming convention in CSS among developers.
Let's look at our previous example and see how we apply this approach.
Looking at the HTML first, we can see that they're pricing cards. Each card has a class name, called .card
. Inside, there’s a title, price and a list of features.
Let's use the BEM approach and change the .title
to .card__title
. Now, we know that it belongs to the .card
element.
Let's apply the same logic for the other ones, too. Change them from .price
to .card__price
, and from .features
to .card__features
.
.card {
...
}
.bg-primary {
...
}
.card__title {
...
}
.big-text {
...
}
.card__price {
...
}
.card__features {
...
}
Output:
But, what if one of the cards is a popular option and has a different background color. In that case, we add a new class name next to the .card
, called .card--popular
. And, then we can add the different background color to that new class name.
Also, the font size of the price in the popular card is bigger than other ones, so let’s add a new class name to it, called .card__price--big
.
HTML code:
As you can see, BEM makes the code so much easier to read. And, most importantly, it answers the 3 fundamental questions:
- WHAT does it do?
- WHERE could it be used?
- and is it RELATED to other elements?
Next time when you are naming selectors, try to cover all these questions.
Also, if you want to dive deeper into BEM, you read this awesome article.
Okay, let's continue.
Habit #2: Use CSS variables
Here’s a question for you. Do you know what color is this?
.class__name {
background-color: #191970;
}
Well, it’s dark blue. Now, here’s the next question: Is it a primary color, an accent color, or just a random one-off color used in the project?
That’s the problem. Without additional context, like looking at the design file or visiting the website, it’s difficult to know.
Solution?
CSS custom properties (AKA variables)
You might probably have heard the term, DRY (Don't Repeat Yourself) principle. If you find yourself repeating the same values or copy-pasting them across your CSS, you should store them as variables.
Let’s take our color example, and refactor it using variables. A good starting point is naming the variable by the color itself. In our case, it could be:
:root {
--darkblue: #191970;
}
.class__name {
background-color: var(--darkblue);
}
Now that we used this variable, there's a potential problem here. What happens if the color itself changes to red
or green
? Suddenly, the name --darkblue
doesn’t make sense anymore.
To solve this, avoid naming variables based on their literal value. Instead, name them according to their purpose. For example, if this color is the primary color of our project, we can name the variable --primary-color
:
:root {
--primary-color: #191970;
}
.card--popular {
background-color: var(--primary-color);
....
}
button {
background-color: var(--primary-color);
....
}
Now, even if the color changes to green or any other color, variable name still makes sense.
Bonus tip: You can also use variables to store some random or complicated value. It will give a better context on what it is.
/* bad */
.element {
transition: all 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
}
/* good */
:root {
--ease-out: cubic-bezier(0.25, 0.1, 0.25, 1);
}
.element {
transition: all 0.3s var(--ease-out);
}
Habit #3: Comment and organize
Writing comments... Either you like it or hate it.
But, comments in CSS are not just for giving explanations, they can also be used to organize and structure your code.
But, before that let’s first start with the intended use case: adding explanations.
Take a look the following code. Without the comments, can you tell me what it does?
.auto-grid {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
}
How about now?
/* this is for auto-grid, lol */
.auto-grid {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
}
Just kidding, but seriously, how about now?
/**
* Responsive gird that adjusts columns automatically
* Each item has a minimum width of 22rem and a maximum of 100%
**/
.auto-grid {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
}
I know that we all hate writing comments in our code. But, if it's the only thing you can understand, try to give at least some explanations. Trust me, otherwise, you will forget it eventually.
You can ask ChatGPT to give a short and clean comment on how your code works.
Now let’s move to the second use case: organizing and grouping your code with comments.
If you're working with a single CSS file, you might already know that it gets messy real quick. And, it becomes a nightmare especially when you're trying to find that one selector. So, grouping similar or related blocks makes your file and life much easier.
Let's take our previous example, and apply this approach.
In our case, we can separate the global styles and group them in one place, ideally at the top of our file. (I will explain later why) Next, we can separate some of the components that we used in our project and group them together. Now, finally, our main cards. Since, we organized them by their name, we can now easily group them into one place and make it easy to separate from the rest of the code.
If you're not sure about how to actually structure your CSS, you can use the 7-1 approach (https://sass-guidelin.es/#the-7-1-pattern) to understand where each piece of your code belongs to. Although it's designed for Sass folders, you can apply the architecture itself into your single CSS file.
/*------------------------------------*\
#GLOBAL
\*------------------------------------*/
*,
*::before,
*::after {
....
}
body {
....
}
h1,
h2,
h3,
p,
ul,
li {
....
}
/*------------------------------------*\
#COMPONENTS
\*------------------------------------*/
button {
....
}
button:active {
....
}
/*------------------------------------*\
#CARDS
\*------------------------------------*/
.cards {
....
}
.card {
....
}
.card--popular {
....
}
.card__title {
....
}
.card__price {
....
}
.card__price--big {
....
}
.card__features {
....
}
.card__features li::before {
....
}
Also, when you're separating the blocks, make it easy for your eyes to spot them. I use this commenting approach by Harry Roberts in his CSS Guidelines. And, they look clean, too.
/*------------------------------------*\
#FOO
\*------------------------------------*/
Habit #4: Always use classes
Don’t use HTML elements as selectors, like h1
, p
, and button
. You're only allowed to use them when you are applying global styles. But, if it’s something specific, always use classes. Even if it feels like extra step, you will thank yourself in the future.
/* bad */
button { .... }
button:active { .... }
/* good */
.button { .... }
.button:active { .... }
And, speaking of HTML elements, don’t use ID selectors. You might have already heard the term “specificity”. If you haven’t, basically, every selector has its own specificity or “importance” score. You can view it in VS Code by hovering over the selector. The higher the score, the more “important” it is, which means it becomes much difficult to override its style with other lower scored selectors.
You might think it’s a good thing. But, it’s not often the case. If you look at habit #3, we put the general styles at the top of the file. And, components, utilities and other specific styles are placed at the bottom. It’s because general styles are later overridden by specific styles.
/* lose */
.card {
background-color: red;
}
/* win */
.card {
background-color: blue;
}
In CSS, whichever style is applied last will be used. And, in our case, if we use an ID selector somewhere in CSS, and later down the page, try to override its style with a class name, it doesn’t work, because the “specificity” of an id selector is higher than of a class selector.
/* win */
#card {
background-color: red;
}
/* many lines later... */
/* lose */
.card {
background-color: blue;
}
Also, !important
falls in the same category. Don’t use it unless absolutely necessary, such as for debugging why your style is not being applied to your element.
.card {
border: 1px solid red !important; /* only for testing */
}
On a similar note, don’t nest or chain selectors. It also increases the specificity score and makes it harder to maintain. Instead give it a separate class name. If you are unsure about what to name it, refer to habit #1.
/* bad */
.card__features li::before {
....
}
/* good */
.card__feature::before {
....
}
Habit #5: Use appropriate unit
If you’re only using pixels (px
) in your CSS, either you’re a beginner or living under the rock. Because you are missing out on so many cool features that other units can offer. And, it’s important to understand where to use them effectively.
Here’s my general approach on using units in CSS for different cases:
For font sizes
- Use
rem
. Because it's proven to work better than other units, includingpx
. You can read it here, here and here.
For width and height
It's quite tricky.
If it is really specific number, use
rem
.But, if it is not the case, you can use
%
, because they work really well most of the time.vw
andvh
can be also good depending on the use case. But, I often face overflow issue with them. So, be careful.But, if you're controlling the width of a paragraph or text,
ch
is often a good option. It stands for characters, which means you can specify how many characters you want per line.
For paddings, margins and gaps
If you want consistent output that doesn't change and fail, use
rem
.If you want them to change based on font size,
em
works really well.em
unit depends on the font size of the nearby content. You can leverage it to create responsive spacing with fluid font sizes as well.
For other cases
- If you are dealing with very small sizes, like
4px
or8px
where the difference is not that noticeable, you can usepx
. Because0.125rem
or0.375rem
might be difficult to understand at first sight compared to2px
or6px
.
All these units can be overwhelming especially if you want consistent approach in your CSS. But, learning to adopt to different solutions can be a game changer when you understand where to use them.
Outro
And, that's pretty much it. Hope you find this article helpful. If you ever struggle with how to write CSS, you can always come back to this article.
And, of course, there are so many ways of writing CSS, and only limit is your imagination.
Thanks to all developers and their awesome articles. I just gathered them all from years of learning into one hopefully useful article for you.
And, as always, thanks for reading and I’ll see you in the next one.
Top comments (0)