8 CSS selectors DO’s and DON’Ts (part 2)

Some time ago I published a quite popular article called 8 CSS selectors DO’s and DON’Ts, I recommend you to check it out before reading this second part.

Never use generic words for classes as the only CSS selector

Don’t

.highlighted {
  font-weight: bold;
}

Do

.user-name.highlighted {
  font-weight: bold;
}

or

.user-name-highlighted {
  font-weight: bold;
}

Even if you don’t think so in the beginning, you will probably end up having to use highlighted as a class in many other elements, make sure they don’t collide with each other.

Make list/table selectors a plural, and their items a singular

Don’t

.shopping-products { … }
.shopping-products-item { … }

Do

.shopping-products { … }
.shopping-product { … }

Try always to keep class names as short as possible (while still being meaningful).

Prefix JS selectors with js-*

Don’t

<div class=”button open-menu”>

Do

<div class=”button js-open-menu”>

This will make it easier to change your CSS when you modify the layout of your page, since you will not have to worry about breaking any JS functionality.

Another idea I read recently is to use data-* attributes as JS selectors: “Stop Using CSS Selectors for Non-CSS”. I don’t like that one as much because data-* attributes were not thought to be used that way, but it’s still a valid solution. The goal is the same, however: keep CSS selectors and JS selectors separated.

Order selectors by: order of occurrence in the HTML, specificity, box type and how common they are

In the previous article I explained why it’s a good idea to order selectors by the order they appear in the HTML code. When you don’t know that order (for example, you’re writing the CSS for somebody who will write an article) you should order them by specificity. But what if the specificity is the same?

What I do in those cases is moving to the top the elements whose box type is a block or similar, leaving inline elements at the bottom.

After ordering them by block/inline elements, order them by how common they are.

A usual situation where you face this problem is when you’re building an email template: you have to write the styles without knowing how the Marketing team will order the elements when they write the email.

Don’t

ul { ... }
li { ... }
a { ... }
p { ... }

Do

p { ... }
ul { ... }
li { ... }
a { ... }

Let’s see why I ordered the selectors that way in the example above:

  • p: is a block element and usually the most common one.
  • ul: is a block element not very common.
  • li: is a “block” element*, more common than ul but it always appears later.
  • a: is an inline element by default.

* Please notice li is not actually a block element but a list-item. However, list-items behave more similarly to block items than inline ones, same for tables, flexboxes, grids… you get the idea.

It’s better to repeat declarations than selectors

It will make your stylesheets much easier to read and reduce the mental overload to understand them.

Don’t

.map,
.map-svg {
  max-width: 100%;
}
.map {
  background-color: #ddd;
}

Do

.map {
  background-color: #ddd;
  max-width: 100%;
}
.map-svg {
  max-width: 100%;
}

Looking at the examples above, if I ask you “what styles are applied to .map?” it’s easier to answer that question looking at the second example than the first one, even though they do the same and they take the same number of lines.

This is not a strict rule, however. If two selectors share many declarations and they are related to each other, probably it’s better to create a common rule.

In programming there is a mantra:

Making code that works is not difficult, what is difficult is making code that people can understand.

Keep that in mind when writing stylesheets too.

Don’t reset styles you just wrote

Don’t

.menu-item {
  margin-top: 5px;
}
.menu-item:first-child {
  margin-top: 0;
}

Do

.menu-item:not(:first-child) {
  margin-top: 5px;
}

The :not() pseudo-class has existed for years now and it’s well supported by all browsers, so there is no reason to write two rulesets, one creating the styles and the next one resetting them for a specific subset of elements when you could achieve the same with only one ruleset.

Don’t be afraid of the adjacent sibling combinator

Given this markup

<article>
  <h1>We have a header</h1>
  <p class="first">And we want the first letter of the first paragraph after the header to be bold.</p>
  <p>So the first letter of the second paragraph, must not be bold.</p>
</article>

Don’t

h1 {
  font-size: 2em;
}
p.first::first-letter {
  font-weight: bold;
}

Neither

h1 {
  font-size: 2em;
}
p:nth-child(2)::first-letter {
  font-weight: bold;
}

Do

h1 {
  font-size: 2em;
}
h1 + p::first-letter {
  font-weight: bold;
}

Many people doesn’t know about the adjacent sibling combinator. It’s used with a plus sign between two selectors and lets you select and element based on its previous sibling. In the example above, we are selecting a p which is the next sibling of a h1.

It’s is a very handy tool. In many cases it can simplify your markup (you don’t have to add new classes) or your CSS (you don’t have to do gymnastics with nth-child or similar pseudo-classes).

Oh, by the way, there is another cool combinator called general sibling which lets you select any sibling, not only the adjacent one.

Use HTML semantic attributes when possible (specially [hidden])

Don’t

<img src=”…” alt=”…” class=”profile-image hidden”/>
.profile-image.hidden { display: none; }

Do

<img src=”…” alt=”…” class=”profile-image” hidden/>

Using the attribute instead of the .hidden class will let screen-readers and any other machine reading your code understand that element must be ignored.

In addition, it will keep specificity lower, which is always a good thing.

But then how do I animate/transition them?

If you want to animate the visibility of a hidden element you need to force it to have a display value different than none. Then with opacity: *, transform: scale(*) or something similar, you will be able to animate it:

<img src=”…” alt=”…” class=”profile-image” hidden/>
.profile-image {transition: opacity .3s;}
.profile-image[hidden] { display: inline; opacity: 0;}

So in that case you will have to add one more line of CSS, but it’s still worth to do it this way.


Remember to check out the first part of this article if you hadn’t done it yet: “8 CSS selectors DO’s and DON’Ts”.

And press the clap button once (at least!) for every tip that you found useful.

1 Reply to “8 CSS selectors DO’s and DON’Ts (part 2)“

Leave a Reply